@aranova/tracking-next 0.5.1 → 0.6.1

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/server.d.mts CHANGED
@@ -1,5 +1,12 @@
1
- import { b as TrackingParams } from './types-Gp0ioRiQ.mjs';
1
+ import { b as TrackingParams } from './types-B4VZHWnc.mjs';
2
2
 
3
+ /**
4
+ * Read captured attribution params from Next.js server cookies.
5
+ *
6
+ * Use this in React Server Components, route handlers, or server actions when
7
+ * you need access to `gclid`, `fbclid`, or UTM values before client JavaScript
8
+ * runs.
9
+ */
3
10
  declare function getTrackingParamsServer(): TrackingParams;
4
11
 
5
12
  export { getTrackingParamsServer };
package/dist/server.d.ts CHANGED
@@ -1,5 +1,12 @@
1
- import { b as TrackingParams } from './types-Gp0ioRiQ.js';
1
+ import { b as TrackingParams } from './types-B4VZHWnc.js';
2
2
 
3
+ /**
4
+ * Read captured attribution params from Next.js server cookies.
5
+ *
6
+ * Use this in React Server Components, route handlers, or server actions when
7
+ * you need access to `gclid`, `fbclid`, or UTM values before client JavaScript
8
+ * runs.
9
+ */
3
10
  declare function getTrackingParamsServer(): TrackingParams;
4
11
 
5
12
  export { getTrackingParamsServer };
package/dist/server.js CHANGED
@@ -68,7 +68,7 @@ var pageViewMetadataSchema = import_zod.z.object({
68
68
  path: import_zod.z.string(),
69
69
  search: import_zod.z.string(),
70
70
  hash: import_zod.z.string()
71
- }),
71
+ }).strict(),
72
72
  referrer: import_zod.z.string().nullable(),
73
73
  // `.nullable().optional()` — absent (undefined) OR explicit null OR a
74
74
  // real viewport object. Mirrors Pydantic's `_Viewport | None = None`
@@ -76,7 +76,7 @@ var pageViewMetadataSchema = import_zod.z.object({
76
76
  viewport: import_zod.z.object({
77
77
  w: import_zod.z.number(),
78
78
  h: import_zod.z.number()
79
- }).nullable().optional()
79
+ }).strict().nullable().optional()
80
80
  }).strict();
81
81
  var pageViewConfigSchema = import_zod.z.object({}).strict();
82
82
 
@@ -86,7 +86,7 @@ var ctaClickMetadataSchema = import_zod2.z.object({
86
86
  cta_name: import_zod2.z.string(),
87
87
  page: import_zod2.z.object({
88
88
  path: import_zod2.z.string()
89
- }),
89
+ }).strict(),
90
90
  section: import_zod2.z.string().nullable().optional(),
91
91
  destination_url: import_zod2.z.string().nullable().optional()
92
92
  }).strict();
@@ -113,10 +113,10 @@ var formStartMetadataSchema = import_zod4.z.object({
113
113
  form: import_zod4.z.object({
114
114
  id: import_zod4.z.string(),
115
115
  action: import_zod4.z.string().nullable()
116
- }),
116
+ }).strict(),
117
117
  page: import_zod4.z.object({
118
118
  path: import_zod4.z.string()
119
- })
119
+ }).strict()
120
120
  }).strict();
121
121
  var formStartConfigSchema = import_zod4.z.object({
122
122
  selector: import_zod4.z.string().optional()
@@ -124,6 +124,16 @@ var formStartConfigSchema = import_zod4.z.object({
124
124
 
125
125
  // ../tracking-core/src/events/form-submit.ts
126
126
  var import_zod5 = require("zod");
127
+ var jsonValueSchema = import_zod5.z.lazy(
128
+ () => import_zod5.z.union([
129
+ import_zod5.z.string(),
130
+ import_zod5.z.number().finite(),
131
+ import_zod5.z.boolean(),
132
+ import_zod5.z.null(),
133
+ import_zod5.z.array(jsonValueSchema),
134
+ import_zod5.z.record(jsonValueSchema)
135
+ ])
136
+ );
127
137
  var formSubmitMetadataSchema = import_zod5.z.object({
128
138
  form: import_zod5.z.object({
129
139
  id: import_zod5.z.string(),
@@ -133,13 +143,13 @@ var formSubmitMetadataSchema = import_zod5.z.object({
133
143
  name: import_zod5.z.string(),
134
144
  type: import_zod5.z.string(),
135
145
  label: import_zod5.z.string().nullable(),
136
- has_value: import_zod5.z.boolean()
137
- })
146
+ value: jsonValueSchema
147
+ }).strict()
138
148
  ).optional()
139
- }),
149
+ }).strict(),
140
150
  page: import_zod5.z.object({
141
151
  path: import_zod5.z.string()
142
- })
152
+ }).strict()
143
153
  }).strict();
144
154
  var formSubmitConfigSchema = import_zod5.z.object({}).strict();
145
155
 
@@ -149,7 +159,7 @@ var multiPageSessionMetadataSchema = import_zod6.z.object({
149
159
  page_count: import_zod6.z.number().int().min(2),
150
160
  page: import_zod6.z.object({
151
161
  path: import_zod6.z.string()
152
- })
162
+ }).strict()
153
163
  }).strict();
154
164
  var multiPageSessionConfigSchema = import_zod6.z.object({
155
165
  pageThreshold: import_zod6.z.number().int().min(2)
@@ -161,7 +171,7 @@ var phoneClickMetadataSchema = import_zod7.z.object({
161
171
  phone_number: import_zod7.z.string(),
162
172
  page: import_zod7.z.object({
163
173
  path: import_zod7.z.string()
164
- }),
174
+ }).strict(),
165
175
  section: import_zod7.z.string().nullable().optional()
166
176
  }).strict();
167
177
  var phoneClickConfigSchema = import_zod7.z.object({}).strict();
@@ -172,7 +182,7 @@ var scrollDepthMetadataSchema = import_zod8.z.object({
172
182
  depth_percent: import_zod8.z.number().int().min(1).max(100),
173
183
  page: import_zod8.z.object({
174
184
  path: import_zod8.z.string()
175
- })
185
+ }).strict()
176
186
  }).strict();
177
187
  var scrollDepthConfigSchema = import_zod8.z.object({
178
188
  thresholds: import_zod8.z.array(import_zod8.z.number().int().min(1).max(100)).min(1)
@@ -195,7 +205,7 @@ var specificPageVisitMetadataSchema = import_zod9.z.object({
195
205
  page_name: specificPageNameSchema,
196
206
  page: import_zod9.z.object({
197
207
  path: import_zod9.z.string()
198
- })
208
+ }).strict()
199
209
  }).strict();
200
210
  var specificPageVisitConfigSchema = import_zod9.z.object({
201
211
  pages: import_zod9.z.array(
@@ -204,7 +214,7 @@ var specificPageVisitConfigSchema = import_zod9.z.object({
204
214
  pathPattern: import_zod9.z.custom((value) => value instanceof RegExp, {
205
215
  message: "pathPattern must be a RegExp"
206
216
  })
207
- })
217
+ }).strict()
208
218
  ).min(1)
209
219
  }).strict();
210
220
 
@@ -214,7 +224,7 @@ var timeOnSiteMetadataSchema = import_zod10.z.object({
214
224
  duration_ms: import_zod10.z.number().int().nonnegative(),
215
225
  page: import_zod10.z.object({
216
226
  path: import_zod10.z.string()
217
- })
227
+ }).strict()
218
228
  }).strict();
219
229
  var timeOnSiteConfigSchema = import_zod10.z.object({
220
230
  thresholdSeconds: import_zod10.z.number().int().positive()
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/cta-click.ts","../../tracking-core/src/events/sdk-heartbeat.ts","../../tracking-core/src/events/form-start.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/multi-page-session.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/scroll-depth.ts","../../tracking-core/src/events/specific-page-visit.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts"],"sourcesContent":["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","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 interface SessionIdResult {\n id: string;\n isNew: boolean;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): SessionIdResult {\n if (typeof window === 'undefined')\n return { id: safeUuid(), isNew: true };\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 { id: parsed.id, isNew: false };\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 { id: fresh.id, isNew: true };\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","import { z } from 'zod';\n\n// page_view is automatic and auto-emitted by attachAutoPageView() on initial\n// load, SPA route changes (pushState / replaceState / popstate), and bfcache\n// restore (pageshow with event.persisted === true).\n\nexport const pageViewMetadataSchema = z\n .object({\n page: z.object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n }),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n// No configurable options today, but must be registered for the SDK to attach\n// the auto-page-view handler. Reserved for future options like `throttleMs`\n// or `includeHash`.\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n// cta_click is manual. Consumer code calls trackEvent('cta_click', ...) from\n// click handlers on CTAs like \"Get Directions\", \"Book Appointment\", etc.\n// The cta_name is a free-form string chosen by the consumer to identify the\n// action (e.g. \"get_directions\", \"book_appointment\", \"download_brochure\").\n\nexport const ctaClickMetadataSchema = z\n .object({\n cta_name: z.string(),\n page: z.object({\n path: z.string(),\n }),\n section: z.string().nullable().optional(),\n destination_url: z.string().nullable().optional(),\n })\n .strict();\n\nexport type CtaClickMetadata = z.infer<typeof ctaClickMetadataSchema>;\n\nexport const ctaClickConfigSchema = z.object({}).strict();\nexport type CtaClickConfig = z.infer<typeof ctaClickConfigSchema>;\n","import { z } from 'zod';\n\n// sdk_heartbeat is an SDK-internal automatic event fired exactly once at the\n// start of each new session. It carries the SDK version and the complete\n// trigger registry the consumer registered, so the backend knows which\n// events a client site is configured to capture without requiring that\n// information on every single event payload.\n\nexport const sdkHeartbeatTriggersSchema = z.object({\n automatic: z.array(z.string()),\n manual: z.array(z.string()),\n}).strict();\n\nexport const sdkHeartbeatMetadataSchema = z\n .object({\n sdk_version: z.string(),\n package_name: z.string().nullable(),\n surface: z.enum(['next', 'react', 'script']),\n triggers: sdkHeartbeatTriggersSchema,\n trigger_config: z.record(z.string(), z.record(z.string(), z.unknown())).nullable().optional(),\n })\n .strict();\n\nexport type SdkHeartbeatMetadata = z.infer<typeof sdkHeartbeatMetadataSchema>;\n\n// No consumer-facing config — heartbeat is SDK-internal.\nexport const sdkHeartbeatConfigSchema = z.object({}).strict();\nexport type SdkHeartbeatConfig = z.infer<typeof sdkHeartbeatConfigSchema>;\n","import { z } from 'zod';\n\n// form_start is automatic. The SDK uses focusin event delegation on document\n// to detect the first field interaction inside a <form>. Fires once per form\n// per page lifecycle. Resets on SPA navigation.\n\nexport const formStartMetadataSchema = z\n .object({\n form: z.object({\n id: z.string(),\n action: z.string().nullable(),\n }),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type FormStartMetadata = z.infer<typeof formStartMetadataSchema>;\n\nexport const formStartConfigSchema = z\n .object({\n selector: z.string().optional(),\n })\n .strict();\n\nexport type FormStartConfig = z.infer<typeof formStartConfigSchema>;\n","import { z } from 'zod';\n\n// form_submit is manual — the SDK never auto-fires this. Consumer code calls\n// `tracking.trackEvent('form_submit', { form, page })` from their own submit\n// handler. Registering it enables the type-level permission; omitting it\n// turns manual calls into a compile error.\n//\n// The optional `fields` array captures structural metadata about the form\n// fields (name, type, label, whether the field was filled). It NEVER captures\n// actual field values — this is a hard PII boundary. Consumers explicitly\n// build the fields array themselves so they control exactly what is sent.\n\nexport const formSubmitMetadataSchema = z\n .object({\n form: z.object({\n id: z.string(),\n action: z.string().nullable(),\n fields: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n label: z.string().nullable(),\n has_value: z.boolean(),\n }),\n )\n .optional(),\n }),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type FormSubmitMetadata = z.infer<typeof formSubmitMetadataSchema>;\n\nexport const formSubmitConfigSchema = z.object({}).strict();\nexport type FormSubmitConfig = z.infer<typeof formSubmitConfigSchema>;\n","import { z } from 'zod';\n\n// multi_page_session is automatic. The SDK tracks distinct page paths visited\n// in the current session via sessionStorage. When the count reaches the\n// configured threshold, fires once. Resets on session rotation.\n\nexport const multiPageSessionMetadataSchema = z\n .object({\n page_count: z.number().int().min(2),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type MultiPageSessionMetadata = z.infer<typeof multiPageSessionMetadataSchema>;\n\nexport const multiPageSessionConfigSchema = z\n .object({\n pageThreshold: z.number().int().min(2),\n })\n .strict();\n\nexport type MultiPageSessionConfig = z.infer<typeof multiPageSessionConfigSchema>;\n","import { z } from 'zod';\n\n// phone_click is manual. Consumer code calls trackEvent('phone_click', ...)\n// from a click handler on a tel: anchor or similar. The phone_number is the\n// business's number (extracted from the href), not the visitor's. The optional\n// section helps distinguish multiple phone links on the same page.\n\nexport const phoneClickMetadataSchema = z\n .object({\n phone_number: z.string(),\n page: z.object({\n path: z.string(),\n }),\n section: z.string().nullable().optional(),\n })\n .strict();\n\nexport type PhoneClickMetadata = z.infer<typeof phoneClickMetadataSchema>;\n\nexport const phoneClickConfigSchema = z.object({}).strict();\nexport type PhoneClickConfig = z.infer<typeof phoneClickConfigSchema>;\n","import { z } from 'zod';\n\n// scroll_depth is automatic. The SDK installs a requestAnimationFrame-throttled\n// scroll listener and fires once per configured threshold per page. Resets on\n// SPA navigation.\n\nexport const scrollDepthMetadataSchema = z\n .object({\n depth_percent: z.number().int().min(1).max(100),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type ScrollDepthMetadata = z.infer<typeof scrollDepthMetadataSchema>;\n\nexport const scrollDepthConfigSchema = z\n .object({\n thresholds: z.array(z.number().int().min(1).max(100)).min(1),\n })\n .strict();\n\nexport type ScrollDepthConfig = z.infer<typeof scrollDepthConfigSchema>;\n","import { z } from 'zod';\n\n// specific_page_visit is automatic. The SDK observes every navigation and\n// emits a specific_page_visit event when the current pathname matches one of\n// the consumer-provided patterns. Each pattern has a strongly typed name\n// (contact_page, services_page, etc.) so the event stream is self-describing\n// and downstream analytics can group by page intent without re-applying regexes.\n\nexport const SPECIFIC_PAGE_NAMES = [\n 'contact_page',\n 'about_page',\n 'services_page',\n 'booking_page',\n 'location_page',\n 'pricing_page',\n 'faq_page',\n 'testimonials_page',\n] as const;\n\nexport type SpecificPageName = (typeof SPECIFIC_PAGE_NAMES)[number];\n\nexport const specificPageNameSchema = z.enum(SPECIFIC_PAGE_NAMES);\n\nexport const specificPageVisitMetadataSchema = z\n .object({\n page_name: specificPageNameSchema,\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type SpecificPageVisitMetadata = z.infer<typeof specificPageVisitMetadataSchema>;\n\nexport const specificPageVisitConfigSchema = z\n .object({\n pages: z\n .array(\n z.object({\n name: specificPageNameSchema,\n pathPattern: z.custom<RegExp>((value) => value instanceof RegExp, {\n message: 'pathPattern must be a RegExp',\n }),\n }),\n )\n .min(1),\n })\n .strict();\n\nexport type SpecificPageVisitConfig = z.infer<typeof specificPageVisitConfigSchema>;\n","import { z } from 'zod';\n\n// time_on_site is automatic. The SDK starts a visibility-aware timer on page\n// load and fires once when the visible-time threshold is crossed. The timer\n// pauses when the tab goes hidden so a user who walks away from a tab\n// doesn't generate fake engagement.\n\nexport const timeOnSiteMetadataSchema = z\n .object({\n duration_ms: z.number().int().nonnegative(),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type TimeOnSiteMetadata = z.infer<typeof timeOnSiteMetadataSchema>;\n\nexport const timeOnSiteConfigSchema = z\n .object({\n thresholdSeconds: z.number().int().positive(),\n })\n .strict();\nexport type TimeOnSiteConfig = z.infer<typeof timeOnSiteConfigSchema>;\n","import type { z } from 'zod';\n\nimport {\n ctaClickConfigSchema,\n ctaClickMetadataSchema,\n type CtaClickConfig,\n type CtaClickMetadata,\n} from './cta-click';\nimport {\n sdkHeartbeatConfigSchema,\n sdkHeartbeatMetadataSchema,\n type SdkHeartbeatConfig,\n type SdkHeartbeatMetadata,\n} from './sdk-heartbeat';\nimport {\n formStartConfigSchema,\n formStartMetadataSchema,\n type FormStartConfig,\n type FormStartMetadata,\n} from './form-start';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\nimport {\n multiPageSessionConfigSchema,\n multiPageSessionMetadataSchema,\n type MultiPageSessionConfig,\n type MultiPageSessionMetadata,\n} from './multi-page-session';\nimport {\n pageViewConfigSchema,\n pageViewMetadataSchema,\n type PageViewConfig,\n type PageViewMetadata,\n} from './page-view';\nimport {\n phoneClickConfigSchema,\n phoneClickMetadataSchema,\n type PhoneClickConfig,\n type PhoneClickMetadata,\n} from './phone-click';\nimport {\n scrollDepthConfigSchema,\n scrollDepthMetadataSchema,\n type ScrollDepthConfig,\n type ScrollDepthMetadata,\n} from './scroll-depth';\nimport {\n specificPageVisitConfigSchema,\n specificPageVisitMetadataSchema,\n type SpecificPageVisitConfig,\n type SpecificPageVisitMetadata,\n} from './specific-page-visit';\nimport {\n timeOnSiteConfigSchema,\n timeOnSiteMetadataSchema,\n type TimeOnSiteConfig,\n type TimeOnSiteMetadata,\n} from './time-on-site';\n\n// Event kind — automatic events are fired by the SDK itself when their\n// client-side signal fires (page_view on navigation, time_on_site on timer,\n// etc). Manual events are only fireable via explicit consumer code.\nexport type EventKind = 'automatic' | 'manual';\n\n// The registry is the single client-side source of truth for \"what events\n// exist, what shape does their metadata take, and what kind are they\". The\n// backend mirror lives in apps/api/src/schemas/tracking_events.py and is\n// kept in sync via the drift test in apps/api/tests/test_event_schema_drift.py.\nexport const EVENT_REGISTRY = {\n // --- automatic triggers ---\n page_view: {\n kind: 'automatic',\n metadataSchema: pageViewMetadataSchema,\n configSchema: pageViewConfigSchema,\n },\n time_on_site: {\n kind: 'automatic',\n metadataSchema: timeOnSiteMetadataSchema,\n configSchema: timeOnSiteConfigSchema,\n },\n specific_page_visit: {\n kind: 'automatic',\n metadataSchema: specificPageVisitMetadataSchema,\n configSchema: specificPageVisitConfigSchema,\n },\n scroll_depth: {\n kind: 'automatic',\n metadataSchema: scrollDepthMetadataSchema,\n configSchema: scrollDepthConfigSchema,\n },\n multi_page_session: {\n kind: 'automatic',\n metadataSchema: multiPageSessionMetadataSchema,\n configSchema: multiPageSessionConfigSchema,\n },\n form_start: {\n kind: 'automatic',\n metadataSchema: formStartMetadataSchema,\n configSchema: formStartConfigSchema,\n },\n // --- SDK-internal automatic (not consumer-configurable) ---\n sdk_heartbeat: {\n kind: 'automatic',\n metadataSchema: sdkHeartbeatMetadataSchema,\n configSchema: sdkHeartbeatConfigSchema,\n },\n // --- manual triggers ---\n form_submit: {\n kind: 'manual',\n metadataSchema: formSubmitMetadataSchema,\n configSchema: formSubmitConfigSchema,\n },\n phone_click: {\n kind: 'manual',\n metadataSchema: phoneClickMetadataSchema,\n configSchema: phoneClickConfigSchema,\n },\n cta_click: {\n kind: 'manual',\n metadataSchema: ctaClickMetadataSchema,\n configSchema: ctaClickConfigSchema,\n },\n} as const;\n\nexport type EventName = keyof typeof EVENT_REGISTRY;\n\n// Narrow the event name union based on `kind`.\nexport type AutomaticEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'automatic' ? K : never;\n}[EventName];\n\nexport type ManualEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'manual' ? K : never;\n}[EventName];\n\n// Map an event name to its explicit metadata / config TS type. We pull the\n// inferred types directly from the per-event files rather than deriving\n// via `z.infer<typeof EVENT_REGISTRY[K]['metadataSchema']>` so that error\n// messages name the event-specific type (PageViewMetadata, not an\n// anonymous zod inference).\ntype MetadataByName = {\n page_view: PageViewMetadata;\n time_on_site: TimeOnSiteMetadata;\n specific_page_visit: SpecificPageVisitMetadata;\n scroll_depth: ScrollDepthMetadata;\n multi_page_session: MultiPageSessionMetadata;\n form_start: FormStartMetadata;\n sdk_heartbeat: SdkHeartbeatMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n cta_click: CtaClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n specific_page_visit: SpecificPageVisitConfig;\n scroll_depth: ScrollDepthConfig;\n multi_page_session: MultiPageSessionConfig;\n form_start: FormStartConfig;\n sdk_heartbeat: SdkHeartbeatConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\n cta_click: CtaClickConfig;\n};\n\nexport type EventMetadata<K extends EventName> = MetadataByName[K];\nexport type EventConfig<K extends EventName> = ConfigByName[K];\n\n// Runtime constant arrays for iteration at consumer / factory time.\nexport const ALL_AUTOMATIC_EVENT_NAMES: readonly AutomaticEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'automatic')\n .map(([name]) => name) as AutomaticEventName[];\n\nexport const ALL_MANUAL_EVENT_NAMES: readonly ManualEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'manual')\n .map(([name]) => name) as ManualEventName[];\n\n// The shape passed to createTracking({ triggers: ... }).\n//\n// `automatic.page_view` is **required** — if you're installing tracking you\n// always want page views, and forgetting to register it is a footgun we\n// prefer to catch at compile time. Every other automatic event is optional.\n// Manual events are all optional — register only the ones you actually\n// intend to fire from consumer code.\nexport type TriggerRegistryConfig = {\n automatic: {\n page_view: EventConfig<'page_view'>;\n } & Partial<{\n time_on_site: EventConfig<'time_on_site'>;\n specific_page_visit: EventConfig<'specific_page_visit'>;\n scroll_depth: EventConfig<'scroll_depth'>;\n multi_page_session: EventConfig<'multi_page_session'>;\n form_start: EventConfig<'form_start'>;\n }>;\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_click'>;\n cta_click: EventConfig<'cta_click'>;\n }>;\n};\n\n// Given a registry, which manual event names did the consumer actually\n// register? These are the only ones `trackEvent()` will accept as its\n// first argument. `NonNullable` collapses the case where `manual` was\n// omitted entirely.\nexport type RegisteredManualEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof NonNullable<TRegistry['manual']>, ManualEventName>;\n\n// Given a registry, which automatic event names did the consumer register?\n// Used by the factory to decide which auto-trigger attach helpers to wire up.\nexport type RegisteredAutomaticEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof TRegistry['automatic'], AutomaticEventName>;\n\n// Internal helper used by the factory: given a registry, return the full\n// discriminated union of valid (eventName, metadata) pairs the consumer\n// can pass to trackEvent().\nexport type TrackableEvent<TRegistry extends TriggerRegistryConfig> = {\n [K in RegisteredManualEvents<TRegistry>]: {\n eventType: K;\n metadata: EventMetadata<K>;\n };\n}[RegisteredManualEvents<TRegistry>];\n\n// Runtime helper: look up a schema pair by name. Cast through `unknown`\n// because the registry is `as const` and TS loses the specific schema type\n// when indexing via a dynamic key.\nexport function getEventDefinition(name: EventName): {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n} {\n return EVENT_REGISTRY[name] as unknown as {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n };\n}\n"],"mappings":"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","names":["import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod"]}
1
+ {"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/cta-click.ts","../../tracking-core/src/events/sdk-heartbeat.ts","../../tracking-core/src/events/form-start.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/multi-page-session.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/scroll-depth.ts","../../tracking-core/src/events/specific-page-visit.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts"],"sourcesContent":["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\n/**\n * Read captured attribution params from Next.js server cookies.\n *\n * Use this in React Server Components, route handlers, or server actions when\n * you need access to `gclid`, `fbclid`, or UTM values before client JavaScript\n * runs.\n */\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n","import type { TrackingParams } from './types';\n\n/**\n * Default attribution cookie lifetime: 90 days.\n */\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\n/**\n * Attribution query/cookie keys captured by the SDK.\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\n/**\n * Create an all-null attribution parameter object.\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\n/**\n * Normalize a raw cookie value into a tracking value.\n *\n * Empty strings and non-string values become `null`.\n */\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\n/**\n * Read all tracking params using the provided cookie reader.\n *\n * This is shared by browser, React, and Next server integrations.\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\n/**\n * Extract tracking params from URL search params.\n *\n * Only non-empty values are returned.\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\n/**\n * Read a tracking cookie from `document.cookie`.\n *\n * Returns `null` during SSR or when the cookie is missing.\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\n/**\n * Persist one attribution value as a first-party cookie.\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\n/**\n * Capture any tracking params present in a URL search parameter source and\n * persist them to first-party cookies.\n *\n * Returns the subset of params that were found and persisted.\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\n/**\n * Capture tracking params from a URL, persist them to first-party cookies, and\n * return the current cookie-backed attribution state.\n *\n * Defaults to `window.location.href` in the browser.\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\n/**\n * localStorage key for the persistent visitor id.\n */\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\n\n/**\n * localStorage key for the rolling session id state.\n */\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\n\n/**\n * Idle window before a new session id is created.\n */\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\n/**\n * Serialized session state stored in localStorage.\n */\nexport interface StoredSession {\n /** Client-generated session UUID. */\n id: string;\n /** Unix timestamp in milliseconds for the most recent event/session touch. */\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\n/**\n * Return the persistent visitor id for this browser profile.\n *\n * Creates and stores a new id when one does not already exist. During SSR,\n * returns an ephemeral id because browser storage is unavailable.\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\n/**\n * Result from `getOrRotateSessionId()`.\n */\nexport interface SessionIdResult {\n /** Current session id. */\n id: string;\n /** Whether this call created a new session. */\n isNew: boolean;\n}\n\n/**\n * Return the current session id, rotating it after the idle window expires.\n *\n * Also refreshes `last_event_at` for active sessions.\n */\nexport function getOrRotateSessionId(now: number = Date.now()): SessionIdResult {\n if (typeof window === 'undefined')\n return { id: safeUuid(), isNew: true };\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 { id: parsed.id, isNew: false };\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 { id: fresh.id, isNew: true };\n}\n\n/**\n * Clear visitor and session identity from localStorage.\n *\n * Intended for tests, debugging, and explicit user reset flows.\n */\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","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `page_view` event.\n *\n * The SDK emits this on initial load, SPA route changes, and bfcache restores.\n * Consumers do not call `trackEvent('page_view', ...)`; registering\n * `automatic: { page_view: {} }` enables the SDK-owned trigger.\n */\nexport const pageViewMetadataSchema = z\n .object({\n page: z\n .object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n })\n .strict(),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .strict()\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n/**\n * Registration config for automatic `page_view`.\n *\n * `page_view` is required in every trigger registry and currently has no\n * options. Use `{ page_view: {} }`.\n */\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for a manually fired `cta_click` event.\n *\n * Use this for non-phone calls to action such as directions, appointment\n * buttons, downloads, or external booking links.\n */\nexport const ctaClickMetadataSchema = z\n .object({\n cta_name: z.string(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n section: z.string().nullable().optional(),\n destination_url: z.string().nullable().optional(),\n })\n .strict();\n\nexport type CtaClickMetadata = z.infer<typeof ctaClickMetadataSchema>;\n\n/**\n * Registration config for `cta_click`.\n *\n * This event is manual-only and currently has no registration options.\n */\nexport const ctaClickConfigSchema = z.object({}).strict();\nexport type CtaClickConfig = z.infer<typeof ctaClickConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Registered trigger names reported by the SDK heartbeat.\n */\nexport const sdkHeartbeatTriggersSchema = z.object({\n automatic: z.array(z.string()),\n manual: z.array(z.string()),\n}).strict();\n\n/**\n * Metadata for the SDK-internal `sdk_heartbeat` event.\n *\n * The SDK fires this once per new session so the dashboard can show which SDK\n * version, install surface, and trigger registry a client site is running.\n * Consumers do not manually register or fire this event.\n */\nexport const sdkHeartbeatMetadataSchema = z\n .object({\n sdk_version: z.string(),\n package_name: z.string().nullable(),\n surface: z.enum(['next', 'react', 'script']),\n triggers: sdkHeartbeatTriggersSchema,\n trigger_config: z.record(z.string(), z.record(z.string(), z.unknown())).nullable().optional(),\n })\n .strict();\n\nexport type SdkHeartbeatMetadata = z.infer<typeof sdkHeartbeatMetadataSchema>;\n\n/**\n * Internal registration config for `sdk_heartbeat`.\n *\n * This event has no consumer-facing options.\n */\nexport const sdkHeartbeatConfigSchema = z.object({}).strict();\nexport type SdkHeartbeatConfig = z.infer<typeof sdkHeartbeatConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `form_start` event.\n *\n * The SDK emits this once per form when the visitor first focuses a field.\n */\nexport const formStartMetadataSchema = z\n .object({\n form: z\n .object({\n id: z.string(),\n action: z.string().nullable(),\n })\n .strict(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type FormStartMetadata = z.infer<typeof formStartMetadataSchema>;\n\n/**\n * Registration config for automatic `form_start`.\n *\n * Use `selector` to narrow which forms can trigger the event. When omitted,\n * the SDK observes all `<form>` elements.\n */\nexport const formStartConfigSchema = z\n .object({\n selector: z.string().optional(),\n })\n .strict();\n\nexport type FormStartConfig = z.infer<typeof formStartConfigSchema>;\n","import { z } from 'zod';\n\n// form_submit is manual — the SDK never auto-fires this. Consumer code calls\n// `tracking.trackEvent('form_submit', { form, page })` from their own submit\n// handler. Registering it enables the type-level permission; omitting it\n// turns manual calls into a compile error.\n//\n// The optional `fields` array captures submitted form field metadata and JSON\n// values. Consumers explicitly build the fields array themselves so they\n// control exactly what is sent.\n\n/**\n * JSON-serializable value accepted by `form_submit.fields[].value`.\n *\n * This intentionally excludes `undefined`, functions, symbols, `Date`\n * instances, and non-finite numbers. Values are stored in PostgreSQL JSONB, so\n * consumers should send only data that has a stable JSON representation.\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number().finite(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(jsonValueSchema),\n ]),\n);\n\n/**\n * Metadata for a manually fired `form_submit` event.\n *\n * Register the event with `manual: { form_submit: {} }`, then call\n * `trackEvent('form_submit', metadata)` from the host site's submit handler.\n *\n * `fields` is optional. If present, each field value must be JSON-serializable\n * and should be explicitly allowlisted by the integration. Do not send names,\n * emails, visitor phone numbers, addresses, payment data, medical details,\n * passwords, file contents, or free-text messages.\n *\n * @example\n * ```ts\n * tracking.trackEvent('form_submit', {\n * form: {\n * id: 'lead-form',\n * action: '/api/lead',\n * fields: [\n * {\n * name: 'service_interest',\n * type: 'select',\n * label: 'Service interest',\n * value: 'teeth_whitening',\n * },\n * ],\n * },\n * page: { path: window.location.pathname },\n * });\n * ```\n */\nexport const formSubmitMetadataSchema = z\n .object({\n form: z\n .object({\n id: z.string(),\n action: z.string().nullable(),\n fields: z\n .array(\n z\n .object({\n name: z.string(),\n type: z.string(),\n label: z.string().nullable(),\n value: jsonValueSchema,\n })\n .strict(),\n )\n .optional(),\n })\n .strict(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type FormSubmitMetadata = z.infer<typeof formSubmitMetadataSchema>;\n\n/**\n * Registration config for `form_submit`.\n *\n * This event is manual-only and currently has no registration options. The\n * empty object enables typed `trackEvent('form_submit', ...)` calls.\n */\nexport const formSubmitConfigSchema = z.object({}).strict();\nexport type FormSubmitConfig = z.infer<typeof formSubmitConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `multi_page_session` event.\n *\n * Fired when the visitor reaches the configured distinct-page threshold in a\n * single tracking session.\n */\nexport const multiPageSessionMetadataSchema = z\n .object({\n page_count: z.number().int().min(2),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type MultiPageSessionMetadata = z.infer<typeof multiPageSessionMetadataSchema>;\n\n/**\n * Registration config for automatic `multi_page_session`.\n */\nexport const multiPageSessionConfigSchema = z\n .object({\n pageThreshold: z.number().int().min(2),\n })\n .strict();\n\nexport type MultiPageSessionConfig = z.infer<typeof multiPageSessionConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for a manually fired `phone_click` event.\n *\n * `phone_number` should be the business phone number from the clicked `tel:`\n * link, not a visitor-entered phone number. `section` can distinguish header,\n * footer, hero, or contact-page links.\n */\nexport const phoneClickMetadataSchema = z\n .object({\n phone_number: z.string(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n section: z.string().nullable().optional(),\n })\n .strict();\n\nexport type PhoneClickMetadata = z.infer<typeof phoneClickMetadataSchema>;\n\n/**\n * Registration config for `phone_click`.\n *\n * This event is manual-only and currently has no registration options.\n */\nexport const phoneClickConfigSchema = z.object({}).strict();\nexport type PhoneClickConfig = z.infer<typeof phoneClickConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `scroll_depth` event.\n *\n * Fired once per configured threshold per page.\n */\nexport const scrollDepthMetadataSchema = z\n .object({\n depth_percent: z.number().int().min(1).max(100),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type ScrollDepthMetadata = z.infer<typeof scrollDepthMetadataSchema>;\n\n/**\n * Registration config for automatic `scroll_depth`.\n *\n * `thresholds` are integer percentages from 1 to 100.\n */\nexport const scrollDepthConfigSchema = z\n .object({\n thresholds: z.array(z.number().int().min(1).max(100)).min(1),\n })\n .strict();\n\nexport type ScrollDepthConfig = z.infer<typeof scrollDepthConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Canonical page intent names supported by `specific_page_visit`.\n */\nexport const SPECIFIC_PAGE_NAMES = [\n 'contact_page',\n 'about_page',\n 'services_page',\n 'booking_page',\n 'location_page',\n 'pricing_page',\n 'faq_page',\n 'testimonials_page',\n] as const;\n\nexport type SpecificPageName = (typeof SPECIFIC_PAGE_NAMES)[number];\n\nexport const specificPageNameSchema = z.enum(SPECIFIC_PAGE_NAMES);\n\n/**\n * Metadata for the automatic `specific_page_visit` event.\n *\n * The SDK emits this when the current pathname matches one of the configured\n * named page patterns.\n */\nexport const specificPageVisitMetadataSchema = z\n .object({\n page_name: specificPageNameSchema,\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type SpecificPageVisitMetadata = z.infer<typeof specificPageVisitMetadataSchema>;\n\n/**\n * Registration config for automatic `specific_page_visit`.\n *\n * Each page entry pairs a semantic `name` with a `RegExp` that matches the\n * pathname. Use this instead of hard-coding path regexes downstream.\n */\nexport const specificPageVisitConfigSchema = z\n .object({\n pages: z\n .array(\n z\n .object({\n name: specificPageNameSchema,\n pathPattern: z.custom<RegExp>((value) => value instanceof RegExp, {\n message: 'pathPattern must be a RegExp',\n }),\n })\n .strict(),\n )\n .min(1),\n })\n .strict();\n\nexport type SpecificPageVisitConfig = z.infer<typeof specificPageVisitConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `time_on_site` event.\n *\n * The SDK starts a visibility-aware timer and fires once when visible\n * engagement crosses the configured threshold.\n */\nexport const timeOnSiteMetadataSchema = z\n .object({\n duration_ms: z.number().int().nonnegative(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type TimeOnSiteMetadata = z.infer<typeof timeOnSiteMetadataSchema>;\n\n/**\n * Registration config for automatic `time_on_site`.\n */\nexport const timeOnSiteConfigSchema = z\n .object({\n thresholdSeconds: z.number().int().positive(),\n })\n .strict();\nexport type TimeOnSiteConfig = z.infer<typeof timeOnSiteConfigSchema>;\n","import type { z } from 'zod';\n\nimport {\n ctaClickConfigSchema,\n ctaClickMetadataSchema,\n type CtaClickConfig,\n type CtaClickMetadata,\n} from './cta-click';\nimport {\n sdkHeartbeatConfigSchema,\n sdkHeartbeatMetadataSchema,\n type SdkHeartbeatConfig,\n type SdkHeartbeatMetadata,\n} from './sdk-heartbeat';\nimport {\n formStartConfigSchema,\n formStartMetadataSchema,\n type FormStartConfig,\n type FormStartMetadata,\n} from './form-start';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\nimport {\n multiPageSessionConfigSchema,\n multiPageSessionMetadataSchema,\n type MultiPageSessionConfig,\n type MultiPageSessionMetadata,\n} from './multi-page-session';\nimport {\n pageViewConfigSchema,\n pageViewMetadataSchema,\n type PageViewConfig,\n type PageViewMetadata,\n} from './page-view';\nimport {\n phoneClickConfigSchema,\n phoneClickMetadataSchema,\n type PhoneClickConfig,\n type PhoneClickMetadata,\n} from './phone-click';\nimport {\n scrollDepthConfigSchema,\n scrollDepthMetadataSchema,\n type ScrollDepthConfig,\n type ScrollDepthMetadata,\n} from './scroll-depth';\nimport {\n specificPageVisitConfigSchema,\n specificPageVisitMetadataSchema,\n type SpecificPageVisitConfig,\n type SpecificPageVisitMetadata,\n} from './specific-page-visit';\nimport {\n timeOnSiteConfigSchema,\n timeOnSiteMetadataSchema,\n type TimeOnSiteConfig,\n type TimeOnSiteMetadata,\n} from './time-on-site';\n\n// Event kind — automatic events are fired by the SDK itself when their\n// client-side signal fires (page_view on navigation, time_on_site on timer,\n// etc). Manual events are only fireable via explicit consumer code.\nexport type EventKind = 'automatic' | 'manual';\n\n// The registry is the single client-side source of truth for \"what events\n// exist, what shape does their metadata take, and what kind are they\". The\n// backend mirror lives in apps/api/src/schemas/tracking_events.py and is\n// kept in sync via the drift test in apps/api/tests/test_event_schema_drift.py.\nexport const EVENT_REGISTRY = {\n // --- automatic triggers ---\n page_view: {\n kind: 'automatic',\n metadataSchema: pageViewMetadataSchema,\n configSchema: pageViewConfigSchema,\n },\n time_on_site: {\n kind: 'automatic',\n metadataSchema: timeOnSiteMetadataSchema,\n configSchema: timeOnSiteConfigSchema,\n },\n specific_page_visit: {\n kind: 'automatic',\n metadataSchema: specificPageVisitMetadataSchema,\n configSchema: specificPageVisitConfigSchema,\n },\n scroll_depth: {\n kind: 'automatic',\n metadataSchema: scrollDepthMetadataSchema,\n configSchema: scrollDepthConfigSchema,\n },\n multi_page_session: {\n kind: 'automatic',\n metadataSchema: multiPageSessionMetadataSchema,\n configSchema: multiPageSessionConfigSchema,\n },\n form_start: {\n kind: 'automatic',\n metadataSchema: formStartMetadataSchema,\n configSchema: formStartConfigSchema,\n },\n // --- SDK-internal automatic (not consumer-configurable) ---\n sdk_heartbeat: {\n kind: 'automatic',\n metadataSchema: sdkHeartbeatMetadataSchema,\n configSchema: sdkHeartbeatConfigSchema,\n },\n // --- manual triggers ---\n form_submit: {\n kind: 'manual',\n metadataSchema: formSubmitMetadataSchema,\n configSchema: formSubmitConfigSchema,\n },\n phone_click: {\n kind: 'manual',\n metadataSchema: phoneClickMetadataSchema,\n configSchema: phoneClickConfigSchema,\n },\n cta_click: {\n kind: 'manual',\n metadataSchema: ctaClickMetadataSchema,\n configSchema: ctaClickConfigSchema,\n },\n} as const;\n\n/**\n * Name of any event known to the tracking SDK.\n */\nexport type EventName = keyof typeof EVENT_REGISTRY;\n\n/**\n * Event names that are fired by the SDK when their configured signal occurs.\n *\n * Automatic events are not accepted by the typed `trackEvent()` API.\n */\nexport type AutomaticEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'automatic' ? K : never;\n}[EventName];\n\n/**\n * Event names that consumer code can fire manually after registering them.\n */\nexport type ManualEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'manual' ? K : never;\n}[EventName];\n\n// Map an event name to its explicit metadata / config TS type. We pull the\n// inferred types directly from the per-event files rather than deriving\n// via `z.infer<typeof EVENT_REGISTRY[K]['metadataSchema']>` so that error\n// messages name the event-specific type (PageViewMetadata, not an\n// anonymous zod inference).\ntype MetadataByName = {\n page_view: PageViewMetadata;\n time_on_site: TimeOnSiteMetadata;\n specific_page_visit: SpecificPageVisitMetadata;\n scroll_depth: ScrollDepthMetadata;\n multi_page_session: MultiPageSessionMetadata;\n form_start: FormStartMetadata;\n sdk_heartbeat: SdkHeartbeatMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n cta_click: CtaClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n specific_page_visit: SpecificPageVisitConfig;\n scroll_depth: ScrollDepthConfig;\n multi_page_session: MultiPageSessionConfig;\n form_start: FormStartConfig;\n sdk_heartbeat: SdkHeartbeatConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\n cta_click: CtaClickConfig;\n};\n\n/**\n * Metadata payload type for a specific tracking event.\n *\n * @example\n * ```ts\n * type SubmitMetadata = EventMetadata<'form_submit'>;\n * ```\n */\nexport type EventMetadata<K extends EventName> = MetadataByName[K];\n\n/**\n * Trigger registration config type for a specific tracking event.\n */\nexport type EventConfig<K extends EventName> = ConfigByName[K];\n\n// Runtime constant arrays for iteration at consumer / factory time.\n/**\n * Runtime list of automatic event names.\n */\nexport const ALL_AUTOMATIC_EVENT_NAMES: readonly AutomaticEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'automatic')\n .map(([name]) => name) as AutomaticEventName[];\n\n/**\n * Runtime list of manual event names.\n */\nexport const ALL_MANUAL_EVENT_NAMES: readonly ManualEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'manual')\n .map(([name]) => name) as ManualEventName[];\n\n/**\n * Trigger registry passed to `createTracking({ triggers })`.\n *\n * `automatic.page_view` is required because every install should capture page\n * views. Other automatic events are opt-in. Manual events must be registered\n * here before the typed client accepts `trackEvent()` calls for them.\n *\n * @example\n * ```ts\n * createTracking({\n * apiKey,\n * endpoint,\n * triggers: {\n * automatic: {\n * page_view: {},\n * time_on_site: { thresholdSeconds: 60 },\n * },\n * manual: {\n * form_submit: {},\n * phone_click: {},\n * },\n * },\n * });\n * ```\n */\nexport type TriggerRegistryConfig = {\n automatic: {\n page_view: EventConfig<'page_view'>;\n } & Partial<{\n time_on_site: EventConfig<'time_on_site'>;\n specific_page_visit: EventConfig<'specific_page_visit'>;\n scroll_depth: EventConfig<'scroll_depth'>;\n multi_page_session: EventConfig<'multi_page_session'>;\n form_start: EventConfig<'form_start'>;\n }>;\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_click'>;\n cta_click: EventConfig<'cta_click'>;\n }>;\n};\n\n/**\n * Manual event names registered in a concrete trigger registry.\n *\n * Used by `TypedTrackingClient` so `trackEvent()` only accepts events the\n * consumer explicitly enabled.\n */\nexport type RegisteredManualEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof NonNullable<TRegistry['manual']>, ManualEventName>;\n\n/**\n * Automatic event names registered in a concrete trigger registry.\n */\nexport type RegisteredAutomaticEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof TRegistry['automatic'], AutomaticEventName>;\n\n/**\n * Discriminated union of valid manual tracking calls for a registry.\n */\nexport type TrackableEvent<TRegistry extends TriggerRegistryConfig> = {\n [K in RegisteredManualEvents<TRegistry>]: {\n eventType: K;\n metadata: EventMetadata<K>;\n };\n}[RegisteredManualEvents<TRegistry>];\n\n// Runtime helper: look up a schema pair by name. Cast through `unknown`\n// because the registry is `as const` and TS loses the specific schema type\n// when indexing via a dynamic key.\nexport function getEventDefinition(name: EventName): {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n} {\n return EVENT_REGISTRY[name] as unknown as {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAO;AAEP,qBAAwB;;;ACQjB,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,4BAA4C;AAC1D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AACF;AAOO,SAAS,6BAA6B,OAA+B;AAC1E,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAOO,SAAS,kCACd,YACgB;AAChB,SAAO,oBAAoB,OAAuB,CAAC,QAAQ,QAAQ;AACjE,WAAO,GAAG,IAAI,6BAA6B,WAAW,GAAG,CAAC;AAC1D,WAAO;AAAA,EACT,GAAG,0BAA0B,CAAC;AAChC;;;ACpCO,IAAM,kBAAkB,KAAK,KAAK;;;ACtBzC,iBAAkB;AASX,IAAM,yBAAyB,aACnC,OAAO;AAAA,EACN,MAAM,aACH,OAAO;AAAA,IACN,OAAO,aAAE,OAAO,EAAE,SAAS;AAAA,IAC3B,MAAM,aAAE,OAAO;AAAA,IACf,QAAQ,aAAE,OAAO;AAAA,IACjB,MAAM,aAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AAAA,EACV,UAAU,aAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA,EAI9B,UAAU,aACP,OAAO;AAAA,IACN,GAAG,aAAE,OAAO;AAAA,IACZ,GAAG,aAAE,OAAO;AAAA,EACd,CAAC,EACA,OAAO,EACP,SAAS,EACT,SAAS;AACd,CAAC,EACA,OAAO;AAUH,IAAM,uBAAuB,aAAE,OAAO,CAAC,CAAC,EAAE,OAAO;;;AC1CxD,IAAAA,cAAkB;AAQX,IAAM,yBAAyB,cACnC,OAAO;AAAA,EACN,UAAU,cAAE,OAAO;AAAA,EACnB,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AAAA,EACV,SAAS,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACxC,iBAAiB,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAClD,CAAC,EACA,OAAO;AASH,IAAM,uBAAuB,cAAE,OAAO,CAAC,CAAC,EAAE,OAAO;;;AC5BxD,IAAAC,cAAkB;AAKX,IAAM,6BAA6B,cAAE,OAAO;AAAA,EACjD,WAAW,cAAE,MAAM,cAAE,OAAO,CAAC;AAAA,EAC7B,QAAQ,cAAE,MAAM,cAAE,OAAO,CAAC;AAC5B,CAAC,EAAE,OAAO;AASH,IAAM,6BAA6B,cACvC,OAAO;AAAA,EACN,aAAa,cAAE,OAAO;AAAA,EACtB,cAAc,cAAE,OAAO,EAAE,SAAS;AAAA,EAClC,SAAS,cAAE,KAAK,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAAA,EAC3C,UAAU;AAAA,EACV,gBAAgB,cAAE,OAAO,cAAE,OAAO,GAAG,cAAE,OAAO,cAAE,OAAO,GAAG,cAAE,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS;AAC9F,CAAC,EACA,OAAO;AASH,IAAM,2BAA2B,cAAE,OAAO,CAAC,CAAC,EAAE,OAAO;;;AClC5D,IAAAC,cAAkB;AAOX,IAAM,0BAA0B,cACpC,OAAO;AAAA,EACN,MAAM,cACH,OAAO;AAAA,IACN,IAAI,cAAE,OAAO;AAAA,IACb,QAAQ,cAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,CAAC,EACA,OAAO;AAAA,EACV,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AACZ,CAAC,EACA,OAAO;AAUH,IAAM,wBAAwB,cAClC,OAAO;AAAA,EACN,UAAU,cAAE,OAAO,EAAE,SAAS;AAChC,CAAC,EACA,OAAO;;;ACnCV,IAAAC,cAAkB;AA0BX,IAAM,kBAAwC,cAAE;AAAA,EAAK,MAC1D,cAAE,MAAM;AAAA,IACN,cAAE,OAAO;AAAA,IACT,cAAE,OAAO,EAAE,OAAO;AAAA,IAClB,cAAE,QAAQ;AAAA,IACV,cAAE,KAAK;AAAA,IACP,cAAE,MAAM,eAAe;AAAA,IACvB,cAAE,OAAO,eAAe;AAAA,EAC1B,CAAC;AACH;AAgCO,IAAM,2BAA2B,cACrC,OAAO;AAAA,EACN,MAAM,cACH,OAAO;AAAA,IACN,IAAI,cAAE,OAAO;AAAA,IACb,QAAQ,cAAE,OAAO,EAAE,SAAS;AAAA,IAC5B,QAAQ,cACL;AAAA,MACC,cACG,OAAO;AAAA,QACN,MAAM,cAAE,OAAO;AAAA,QACf,MAAM,cAAE,OAAO;AAAA,QACf,OAAO,cAAE,OAAO,EAAE,SAAS;AAAA,QAC3B,OAAO;AAAA,MACT,CAAC,EACA,OAAO;AAAA,IACZ,EACC,SAAS;AAAA,EACd,CAAC,EACA,OAAO;AAAA,EACV,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AACZ,CAAC,EACA,OAAO;AAUH,IAAM,yBAAyB,cAAE,OAAO,CAAC,CAAC,EAAE,OAAO;;;ACvG1D,IAAAC,cAAkB;AAQX,IAAM,iCAAiC,cAC3C,OAAO;AAAA,EACN,YAAY,cAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;AAAA,EAClC,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AACZ,CAAC,EACA,OAAO;AAOH,IAAM,+BAA+B,cACzC,OAAO;AAAA,EACN,eAAe,cAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;AACvC,CAAC,EACA,OAAO;;;AC5BV,IAAAC,cAAkB;AASX,IAAM,2BAA2B,cACrC,OAAO;AAAA,EACN,cAAc,cAAE,OAAO;AAAA,EACvB,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AAAA,EACV,SAAS,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAC1C,CAAC,EACA,OAAO;AASH,IAAM,yBAAyB,cAAE,OAAO,CAAC,CAAC,EAAE,OAAO;;;AC5B1D,IAAAC,cAAkB;AAOX,IAAM,4BAA4B,cACtC,OAAO;AAAA,EACN,eAAe,cAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC9C,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AACZ,CAAC,EACA,OAAO;AASH,IAAM,0BAA0B,cACpC,OAAO;AAAA,EACN,YAAY,cAAE,MAAM,cAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,CAAC;AAC7D,CAAC,EACA,OAAO;;;AC7BV,IAAAC,cAAkB;AAKX,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,yBAAyB,cAAE,KAAK,mBAAmB;AAQzD,IAAM,kCAAkC,cAC5C,OAAO;AAAA,EACN,WAAW;AAAA,EACX,MAAM,cACH,OAAO;AAAA,IACN,MAAM,cAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AACZ,CAAC,EACA,OAAO;AAUH,IAAM,gCAAgC,cAC1C,OAAO;AAAA,EACN,OAAO,cACJ;AAAA,IACC,cACG,OAAO;AAAA,MACN,MAAM;AAAA,MACN,aAAa,cAAE,OAAe,CAAC,UAAU,iBAAiB,QAAQ;AAAA,QAChE,SAAS;AAAA,MACX,CAAC;AAAA,IACH,CAAC,EACA,OAAO;AAAA,EACZ,EACC,IAAI,CAAC;AACV,CAAC,EACA,OAAO;;;AC5DV,IAAAC,eAAkB;AAQX,IAAM,2BAA2B,eACrC,OAAO;AAAA,EACN,aAAa,eAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC1C,MAAM,eACH,OAAO;AAAA,IACN,MAAM,eAAE,OAAO;AAAA,EACjB,CAAC,EACA,OAAO;AACZ,CAAC,EACA,OAAO;AAOH,IAAM,yBAAyB,eACnC,OAAO;AAAA,EACN,kBAAkB,eAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAC9C,CAAC,EACA,OAAO;;;AC4CH,IAAM,iBAAiB;AAAA;AAAA,EAE5B,WAAW;AAAA,IACT,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,oBAAoB;AAAA,IAClB,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,YAAY;AAAA,IACV,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA;AAAA,EAEA,eAAe;AAAA,IACb,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA;AAAA,EAEA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AACF;AAyEO,IAAM,4BACX,OAAO,QAAQ,cAAc,EAE5B,OAAO,CAAC,CAAC,EAAE,GAAG,MAAM,IAAI,SAAS,WAAW,EAC5C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AAKhB,IAAM,yBACX,OAAO,QAAQ,cAAc,EAE5B,OAAO,CAAC,CAAC,EAAE,GAAG,MAAM,IAAI,SAAS,QAAQ,EACzC,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;;;AbtMvB,SAAS,iBAA8B;AACrC,aAAO,wBAAQ;AACjB;AASO,SAAS,0BAA0C;AACxD,QAAM,cAAc,eAAe;AACnC,SAAO,kCAAkC,CAAC,QAAQ,YAAY,IAAI,GAAG,GAAG,KAAK;AAC/E;","names":["import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod"]}
package/dist/server.mjs CHANGED
@@ -44,7 +44,7 @@ var pageViewMetadataSchema = z.object({
44
44
  path: z.string(),
45
45
  search: z.string(),
46
46
  hash: z.string()
47
- }),
47
+ }).strict(),
48
48
  referrer: z.string().nullable(),
49
49
  // `.nullable().optional()` — absent (undefined) OR explicit null OR a
50
50
  // real viewport object. Mirrors Pydantic's `_Viewport | None = None`
@@ -52,7 +52,7 @@ var pageViewMetadataSchema = z.object({
52
52
  viewport: z.object({
53
53
  w: z.number(),
54
54
  h: z.number()
55
- }).nullable().optional()
55
+ }).strict().nullable().optional()
56
56
  }).strict();
57
57
  var pageViewConfigSchema = z.object({}).strict();
58
58
 
@@ -62,7 +62,7 @@ var ctaClickMetadataSchema = z2.object({
62
62
  cta_name: z2.string(),
63
63
  page: z2.object({
64
64
  path: z2.string()
65
- }),
65
+ }).strict(),
66
66
  section: z2.string().nullable().optional(),
67
67
  destination_url: z2.string().nullable().optional()
68
68
  }).strict();
@@ -89,10 +89,10 @@ var formStartMetadataSchema = z4.object({
89
89
  form: z4.object({
90
90
  id: z4.string(),
91
91
  action: z4.string().nullable()
92
- }),
92
+ }).strict(),
93
93
  page: z4.object({
94
94
  path: z4.string()
95
- })
95
+ }).strict()
96
96
  }).strict();
97
97
  var formStartConfigSchema = z4.object({
98
98
  selector: z4.string().optional()
@@ -100,6 +100,16 @@ var formStartConfigSchema = z4.object({
100
100
 
101
101
  // ../tracking-core/src/events/form-submit.ts
102
102
  import { z as z5 } from "zod";
103
+ var jsonValueSchema = z5.lazy(
104
+ () => z5.union([
105
+ z5.string(),
106
+ z5.number().finite(),
107
+ z5.boolean(),
108
+ z5.null(),
109
+ z5.array(jsonValueSchema),
110
+ z5.record(jsonValueSchema)
111
+ ])
112
+ );
103
113
  var formSubmitMetadataSchema = z5.object({
104
114
  form: z5.object({
105
115
  id: z5.string(),
@@ -109,13 +119,13 @@ var formSubmitMetadataSchema = z5.object({
109
119
  name: z5.string(),
110
120
  type: z5.string(),
111
121
  label: z5.string().nullable(),
112
- has_value: z5.boolean()
113
- })
122
+ value: jsonValueSchema
123
+ }).strict()
114
124
  ).optional()
115
- }),
125
+ }).strict(),
116
126
  page: z5.object({
117
127
  path: z5.string()
118
- })
128
+ }).strict()
119
129
  }).strict();
120
130
  var formSubmitConfigSchema = z5.object({}).strict();
121
131
 
@@ -125,7 +135,7 @@ var multiPageSessionMetadataSchema = z6.object({
125
135
  page_count: z6.number().int().min(2),
126
136
  page: z6.object({
127
137
  path: z6.string()
128
- })
138
+ }).strict()
129
139
  }).strict();
130
140
  var multiPageSessionConfigSchema = z6.object({
131
141
  pageThreshold: z6.number().int().min(2)
@@ -137,7 +147,7 @@ var phoneClickMetadataSchema = z7.object({
137
147
  phone_number: z7.string(),
138
148
  page: z7.object({
139
149
  path: z7.string()
140
- }),
150
+ }).strict(),
141
151
  section: z7.string().nullable().optional()
142
152
  }).strict();
143
153
  var phoneClickConfigSchema = z7.object({}).strict();
@@ -148,7 +158,7 @@ var scrollDepthMetadataSchema = z8.object({
148
158
  depth_percent: z8.number().int().min(1).max(100),
149
159
  page: z8.object({
150
160
  path: z8.string()
151
- })
161
+ }).strict()
152
162
  }).strict();
153
163
  var scrollDepthConfigSchema = z8.object({
154
164
  thresholds: z8.array(z8.number().int().min(1).max(100)).min(1)
@@ -171,7 +181,7 @@ var specificPageVisitMetadataSchema = z9.object({
171
181
  page_name: specificPageNameSchema,
172
182
  page: z9.object({
173
183
  path: z9.string()
174
- })
184
+ }).strict()
175
185
  }).strict();
176
186
  var specificPageVisitConfigSchema = z9.object({
177
187
  pages: z9.array(
@@ -180,7 +190,7 @@ var specificPageVisitConfigSchema = z9.object({
180
190
  pathPattern: z9.custom((value) => value instanceof RegExp, {
181
191
  message: "pathPattern must be a RegExp"
182
192
  })
183
- })
193
+ }).strict()
184
194
  ).min(1)
185
195
  }).strict();
186
196
 
@@ -190,7 +200,7 @@ var timeOnSiteMetadataSchema = z10.object({
190
200
  duration_ms: z10.number().int().nonnegative(),
191
201
  page: z10.object({
192
202
  path: z10.string()
193
- })
203
+ }).strict()
194
204
  }).strict();
195
205
  var timeOnSiteConfigSchema = z10.object({
196
206
  thresholdSeconds: z10.number().int().positive()
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/cta-click.ts","../../tracking-core/src/events/sdk-heartbeat.ts","../../tracking-core/src/events/form-start.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/multi-page-session.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/scroll-depth.ts","../../tracking-core/src/events/specific-page-visit.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts"],"sourcesContent":["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","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 interface SessionIdResult {\n id: string;\n isNew: boolean;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): SessionIdResult {\n if (typeof window === 'undefined')\n return { id: safeUuid(), isNew: true };\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 { id: parsed.id, isNew: false };\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 { id: fresh.id, isNew: true };\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","import { z } from 'zod';\n\n// page_view is automatic and auto-emitted by attachAutoPageView() on initial\n// load, SPA route changes (pushState / replaceState / popstate), and bfcache\n// restore (pageshow with event.persisted === true).\n\nexport const pageViewMetadataSchema = z\n .object({\n page: z.object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n }),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n// No configurable options today, but must be registered for the SDK to attach\n// the auto-page-view handler. Reserved for future options like `throttleMs`\n// or `includeHash`.\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n// cta_click is manual. Consumer code calls trackEvent('cta_click', ...) from\n// click handlers on CTAs like \"Get Directions\", \"Book Appointment\", etc.\n// The cta_name is a free-form string chosen by the consumer to identify the\n// action (e.g. \"get_directions\", \"book_appointment\", \"download_brochure\").\n\nexport const ctaClickMetadataSchema = z\n .object({\n cta_name: z.string(),\n page: z.object({\n path: z.string(),\n }),\n section: z.string().nullable().optional(),\n destination_url: z.string().nullable().optional(),\n })\n .strict();\n\nexport type CtaClickMetadata = z.infer<typeof ctaClickMetadataSchema>;\n\nexport const ctaClickConfigSchema = z.object({}).strict();\nexport type CtaClickConfig = z.infer<typeof ctaClickConfigSchema>;\n","import { z } from 'zod';\n\n// sdk_heartbeat is an SDK-internal automatic event fired exactly once at the\n// start of each new session. It carries the SDK version and the complete\n// trigger registry the consumer registered, so the backend knows which\n// events a client site is configured to capture without requiring that\n// information on every single event payload.\n\nexport const sdkHeartbeatTriggersSchema = z.object({\n automatic: z.array(z.string()),\n manual: z.array(z.string()),\n}).strict();\n\nexport const sdkHeartbeatMetadataSchema = z\n .object({\n sdk_version: z.string(),\n package_name: z.string().nullable(),\n surface: z.enum(['next', 'react', 'script']),\n triggers: sdkHeartbeatTriggersSchema,\n trigger_config: z.record(z.string(), z.record(z.string(), z.unknown())).nullable().optional(),\n })\n .strict();\n\nexport type SdkHeartbeatMetadata = z.infer<typeof sdkHeartbeatMetadataSchema>;\n\n// No consumer-facing config — heartbeat is SDK-internal.\nexport const sdkHeartbeatConfigSchema = z.object({}).strict();\nexport type SdkHeartbeatConfig = z.infer<typeof sdkHeartbeatConfigSchema>;\n","import { z } from 'zod';\n\n// form_start is automatic. The SDK uses focusin event delegation on document\n// to detect the first field interaction inside a <form>. Fires once per form\n// per page lifecycle. Resets on SPA navigation.\n\nexport const formStartMetadataSchema = z\n .object({\n form: z.object({\n id: z.string(),\n action: z.string().nullable(),\n }),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type FormStartMetadata = z.infer<typeof formStartMetadataSchema>;\n\nexport const formStartConfigSchema = z\n .object({\n selector: z.string().optional(),\n })\n .strict();\n\nexport type FormStartConfig = z.infer<typeof formStartConfigSchema>;\n","import { z } from 'zod';\n\n// form_submit is manual — the SDK never auto-fires this. Consumer code calls\n// `tracking.trackEvent('form_submit', { form, page })` from their own submit\n// handler. Registering it enables the type-level permission; omitting it\n// turns manual calls into a compile error.\n//\n// The optional `fields` array captures structural metadata about the form\n// fields (name, type, label, whether the field was filled). It NEVER captures\n// actual field values — this is a hard PII boundary. Consumers explicitly\n// build the fields array themselves so they control exactly what is sent.\n\nexport const formSubmitMetadataSchema = z\n .object({\n form: z.object({\n id: z.string(),\n action: z.string().nullable(),\n fields: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n label: z.string().nullable(),\n has_value: z.boolean(),\n }),\n )\n .optional(),\n }),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type FormSubmitMetadata = z.infer<typeof formSubmitMetadataSchema>;\n\nexport const formSubmitConfigSchema = z.object({}).strict();\nexport type FormSubmitConfig = z.infer<typeof formSubmitConfigSchema>;\n","import { z } from 'zod';\n\n// multi_page_session is automatic. The SDK tracks distinct page paths visited\n// in the current session via sessionStorage. When the count reaches the\n// configured threshold, fires once. Resets on session rotation.\n\nexport const multiPageSessionMetadataSchema = z\n .object({\n page_count: z.number().int().min(2),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type MultiPageSessionMetadata = z.infer<typeof multiPageSessionMetadataSchema>;\n\nexport const multiPageSessionConfigSchema = z\n .object({\n pageThreshold: z.number().int().min(2),\n })\n .strict();\n\nexport type MultiPageSessionConfig = z.infer<typeof multiPageSessionConfigSchema>;\n","import { z } from 'zod';\n\n// phone_click is manual. Consumer code calls trackEvent('phone_click', ...)\n// from a click handler on a tel: anchor or similar. The phone_number is the\n// business's number (extracted from the href), not the visitor's. The optional\n// section helps distinguish multiple phone links on the same page.\n\nexport const phoneClickMetadataSchema = z\n .object({\n phone_number: z.string(),\n page: z.object({\n path: z.string(),\n }),\n section: z.string().nullable().optional(),\n })\n .strict();\n\nexport type PhoneClickMetadata = z.infer<typeof phoneClickMetadataSchema>;\n\nexport const phoneClickConfigSchema = z.object({}).strict();\nexport type PhoneClickConfig = z.infer<typeof phoneClickConfigSchema>;\n","import { z } from 'zod';\n\n// scroll_depth is automatic. The SDK installs a requestAnimationFrame-throttled\n// scroll listener and fires once per configured threshold per page. Resets on\n// SPA navigation.\n\nexport const scrollDepthMetadataSchema = z\n .object({\n depth_percent: z.number().int().min(1).max(100),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type ScrollDepthMetadata = z.infer<typeof scrollDepthMetadataSchema>;\n\nexport const scrollDepthConfigSchema = z\n .object({\n thresholds: z.array(z.number().int().min(1).max(100)).min(1),\n })\n .strict();\n\nexport type ScrollDepthConfig = z.infer<typeof scrollDepthConfigSchema>;\n","import { z } from 'zod';\n\n// specific_page_visit is automatic. The SDK observes every navigation and\n// emits a specific_page_visit event when the current pathname matches one of\n// the consumer-provided patterns. Each pattern has a strongly typed name\n// (contact_page, services_page, etc.) so the event stream is self-describing\n// and downstream analytics can group by page intent without re-applying regexes.\n\nexport const SPECIFIC_PAGE_NAMES = [\n 'contact_page',\n 'about_page',\n 'services_page',\n 'booking_page',\n 'location_page',\n 'pricing_page',\n 'faq_page',\n 'testimonials_page',\n] as const;\n\nexport type SpecificPageName = (typeof SPECIFIC_PAGE_NAMES)[number];\n\nexport const specificPageNameSchema = z.enum(SPECIFIC_PAGE_NAMES);\n\nexport const specificPageVisitMetadataSchema = z\n .object({\n page_name: specificPageNameSchema,\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type SpecificPageVisitMetadata = z.infer<typeof specificPageVisitMetadataSchema>;\n\nexport const specificPageVisitConfigSchema = z\n .object({\n pages: z\n .array(\n z.object({\n name: specificPageNameSchema,\n pathPattern: z.custom<RegExp>((value) => value instanceof RegExp, {\n message: 'pathPattern must be a RegExp',\n }),\n }),\n )\n .min(1),\n })\n .strict();\n\nexport type SpecificPageVisitConfig = z.infer<typeof specificPageVisitConfigSchema>;\n","import { z } from 'zod';\n\n// time_on_site is automatic. The SDK starts a visibility-aware timer on page\n// load and fires once when the visible-time threshold is crossed. The timer\n// pauses when the tab goes hidden so a user who walks away from a tab\n// doesn't generate fake engagement.\n\nexport const timeOnSiteMetadataSchema = z\n .object({\n duration_ms: z.number().int().nonnegative(),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type TimeOnSiteMetadata = z.infer<typeof timeOnSiteMetadataSchema>;\n\nexport const timeOnSiteConfigSchema = z\n .object({\n thresholdSeconds: z.number().int().positive(),\n })\n .strict();\nexport type TimeOnSiteConfig = z.infer<typeof timeOnSiteConfigSchema>;\n","import type { z } from 'zod';\n\nimport {\n ctaClickConfigSchema,\n ctaClickMetadataSchema,\n type CtaClickConfig,\n type CtaClickMetadata,\n} from './cta-click';\nimport {\n sdkHeartbeatConfigSchema,\n sdkHeartbeatMetadataSchema,\n type SdkHeartbeatConfig,\n type SdkHeartbeatMetadata,\n} from './sdk-heartbeat';\nimport {\n formStartConfigSchema,\n formStartMetadataSchema,\n type FormStartConfig,\n type FormStartMetadata,\n} from './form-start';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\nimport {\n multiPageSessionConfigSchema,\n multiPageSessionMetadataSchema,\n type MultiPageSessionConfig,\n type MultiPageSessionMetadata,\n} from './multi-page-session';\nimport {\n pageViewConfigSchema,\n pageViewMetadataSchema,\n type PageViewConfig,\n type PageViewMetadata,\n} from './page-view';\nimport {\n phoneClickConfigSchema,\n phoneClickMetadataSchema,\n type PhoneClickConfig,\n type PhoneClickMetadata,\n} from './phone-click';\nimport {\n scrollDepthConfigSchema,\n scrollDepthMetadataSchema,\n type ScrollDepthConfig,\n type ScrollDepthMetadata,\n} from './scroll-depth';\nimport {\n specificPageVisitConfigSchema,\n specificPageVisitMetadataSchema,\n type SpecificPageVisitConfig,\n type SpecificPageVisitMetadata,\n} from './specific-page-visit';\nimport {\n timeOnSiteConfigSchema,\n timeOnSiteMetadataSchema,\n type TimeOnSiteConfig,\n type TimeOnSiteMetadata,\n} from './time-on-site';\n\n// Event kind — automatic events are fired by the SDK itself when their\n// client-side signal fires (page_view on navigation, time_on_site on timer,\n// etc). Manual events are only fireable via explicit consumer code.\nexport type EventKind = 'automatic' | 'manual';\n\n// The registry is the single client-side source of truth for \"what events\n// exist, what shape does their metadata take, and what kind are they\". The\n// backend mirror lives in apps/api/src/schemas/tracking_events.py and is\n// kept in sync via the drift test in apps/api/tests/test_event_schema_drift.py.\nexport const EVENT_REGISTRY = {\n // --- automatic triggers ---\n page_view: {\n kind: 'automatic',\n metadataSchema: pageViewMetadataSchema,\n configSchema: pageViewConfigSchema,\n },\n time_on_site: {\n kind: 'automatic',\n metadataSchema: timeOnSiteMetadataSchema,\n configSchema: timeOnSiteConfigSchema,\n },\n specific_page_visit: {\n kind: 'automatic',\n metadataSchema: specificPageVisitMetadataSchema,\n configSchema: specificPageVisitConfigSchema,\n },\n scroll_depth: {\n kind: 'automatic',\n metadataSchema: scrollDepthMetadataSchema,\n configSchema: scrollDepthConfigSchema,\n },\n multi_page_session: {\n kind: 'automatic',\n metadataSchema: multiPageSessionMetadataSchema,\n configSchema: multiPageSessionConfigSchema,\n },\n form_start: {\n kind: 'automatic',\n metadataSchema: formStartMetadataSchema,\n configSchema: formStartConfigSchema,\n },\n // --- SDK-internal automatic (not consumer-configurable) ---\n sdk_heartbeat: {\n kind: 'automatic',\n metadataSchema: sdkHeartbeatMetadataSchema,\n configSchema: sdkHeartbeatConfigSchema,\n },\n // --- manual triggers ---\n form_submit: {\n kind: 'manual',\n metadataSchema: formSubmitMetadataSchema,\n configSchema: formSubmitConfigSchema,\n },\n phone_click: {\n kind: 'manual',\n metadataSchema: phoneClickMetadataSchema,\n configSchema: phoneClickConfigSchema,\n },\n cta_click: {\n kind: 'manual',\n metadataSchema: ctaClickMetadataSchema,\n configSchema: ctaClickConfigSchema,\n },\n} as const;\n\nexport type EventName = keyof typeof EVENT_REGISTRY;\n\n// Narrow the event name union based on `kind`.\nexport type AutomaticEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'automatic' ? K : never;\n}[EventName];\n\nexport type ManualEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'manual' ? K : never;\n}[EventName];\n\n// Map an event name to its explicit metadata / config TS type. We pull the\n// inferred types directly from the per-event files rather than deriving\n// via `z.infer<typeof EVENT_REGISTRY[K]['metadataSchema']>` so that error\n// messages name the event-specific type (PageViewMetadata, not an\n// anonymous zod inference).\ntype MetadataByName = {\n page_view: PageViewMetadata;\n time_on_site: TimeOnSiteMetadata;\n specific_page_visit: SpecificPageVisitMetadata;\n scroll_depth: ScrollDepthMetadata;\n multi_page_session: MultiPageSessionMetadata;\n form_start: FormStartMetadata;\n sdk_heartbeat: SdkHeartbeatMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n cta_click: CtaClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n specific_page_visit: SpecificPageVisitConfig;\n scroll_depth: ScrollDepthConfig;\n multi_page_session: MultiPageSessionConfig;\n form_start: FormStartConfig;\n sdk_heartbeat: SdkHeartbeatConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\n cta_click: CtaClickConfig;\n};\n\nexport type EventMetadata<K extends EventName> = MetadataByName[K];\nexport type EventConfig<K extends EventName> = ConfigByName[K];\n\n// Runtime constant arrays for iteration at consumer / factory time.\nexport const ALL_AUTOMATIC_EVENT_NAMES: readonly AutomaticEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'automatic')\n .map(([name]) => name) as AutomaticEventName[];\n\nexport const ALL_MANUAL_EVENT_NAMES: readonly ManualEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'manual')\n .map(([name]) => name) as ManualEventName[];\n\n// The shape passed to createTracking({ triggers: ... }).\n//\n// `automatic.page_view` is **required** — if you're installing tracking you\n// always want page views, and forgetting to register it is a footgun we\n// prefer to catch at compile time. Every other automatic event is optional.\n// Manual events are all optional — register only the ones you actually\n// intend to fire from consumer code.\nexport type TriggerRegistryConfig = {\n automatic: {\n page_view: EventConfig<'page_view'>;\n } & Partial<{\n time_on_site: EventConfig<'time_on_site'>;\n specific_page_visit: EventConfig<'specific_page_visit'>;\n scroll_depth: EventConfig<'scroll_depth'>;\n multi_page_session: EventConfig<'multi_page_session'>;\n form_start: EventConfig<'form_start'>;\n }>;\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_click'>;\n cta_click: EventConfig<'cta_click'>;\n }>;\n};\n\n// Given a registry, which manual event names did the consumer actually\n// register? These are the only ones `trackEvent()` will accept as its\n// first argument. `NonNullable` collapses the case where `manual` was\n// omitted entirely.\nexport type RegisteredManualEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof NonNullable<TRegistry['manual']>, ManualEventName>;\n\n// Given a registry, which automatic event names did the consumer register?\n// Used by the factory to decide which auto-trigger attach helpers to wire up.\nexport type RegisteredAutomaticEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof TRegistry['automatic'], AutomaticEventName>;\n\n// Internal helper used by the factory: given a registry, return the full\n// discriminated union of valid (eventName, metadata) pairs the consumer\n// can pass to trackEvent().\nexport type TrackableEvent<TRegistry extends TriggerRegistryConfig> = {\n [K in RegisteredManualEvents<TRegistry>]: {\n eventType: K;\n metadata: EventMetadata<K>;\n };\n}[RegisteredManualEvents<TRegistry>];\n\n// Runtime helper: look up a schema pair by name. Cast through `unknown`\n// because the registry is `as const` and TS loses the specific schema type\n// when indexing via a dynamic key.\nexport function getEventDefinition(name: EventName): {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n} {\n return EVENT_REGISTRY[name] as unknown as {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n };\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z"]}
1
+ {"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/cta-click.ts","../../tracking-core/src/events/sdk-heartbeat.ts","../../tracking-core/src/events/form-start.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/multi-page-session.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/scroll-depth.ts","../../tracking-core/src/events/specific-page-visit.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts"],"sourcesContent":["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\n/**\n * Read captured attribution params from Next.js server cookies.\n *\n * Use this in React Server Components, route handlers, or server actions when\n * you need access to `gclid`, `fbclid`, or UTM values before client JavaScript\n * runs.\n */\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n","import type { TrackingParams } from './types';\n\n/**\n * Default attribution cookie lifetime: 90 days.\n */\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\n/**\n * Attribution query/cookie keys captured by the SDK.\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\n/**\n * Create an all-null attribution parameter object.\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\n/**\n * Normalize a raw cookie value into a tracking value.\n *\n * Empty strings and non-string values become `null`.\n */\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\n/**\n * Read all tracking params using the provided cookie reader.\n *\n * This is shared by browser, React, and Next server integrations.\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\n/**\n * Extract tracking params from URL search params.\n *\n * Only non-empty values are returned.\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\n/**\n * Read a tracking cookie from `document.cookie`.\n *\n * Returns `null` during SSR or when the cookie is missing.\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\n/**\n * Persist one attribution value as a first-party cookie.\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\n/**\n * Capture any tracking params present in a URL search parameter source and\n * persist them to first-party cookies.\n *\n * Returns the subset of params that were found and persisted.\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\n/**\n * Capture tracking params from a URL, persist them to first-party cookies, and\n * return the current cookie-backed attribution state.\n *\n * Defaults to `window.location.href` in the browser.\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\n/**\n * localStorage key for the persistent visitor id.\n */\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\n\n/**\n * localStorage key for the rolling session id state.\n */\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\n\n/**\n * Idle window before a new session id is created.\n */\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\n/**\n * Serialized session state stored in localStorage.\n */\nexport interface StoredSession {\n /** Client-generated session UUID. */\n id: string;\n /** Unix timestamp in milliseconds for the most recent event/session touch. */\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\n/**\n * Return the persistent visitor id for this browser profile.\n *\n * Creates and stores a new id when one does not already exist. During SSR,\n * returns an ephemeral id because browser storage is unavailable.\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\n/**\n * Result from `getOrRotateSessionId()`.\n */\nexport interface SessionIdResult {\n /** Current session id. */\n id: string;\n /** Whether this call created a new session. */\n isNew: boolean;\n}\n\n/**\n * Return the current session id, rotating it after the idle window expires.\n *\n * Also refreshes `last_event_at` for active sessions.\n */\nexport function getOrRotateSessionId(now: number = Date.now()): SessionIdResult {\n if (typeof window === 'undefined')\n return { id: safeUuid(), isNew: true };\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 { id: parsed.id, isNew: false };\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 { id: fresh.id, isNew: true };\n}\n\n/**\n * Clear visitor and session identity from localStorage.\n *\n * Intended for tests, debugging, and explicit user reset flows.\n */\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","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `page_view` event.\n *\n * The SDK emits this on initial load, SPA route changes, and bfcache restores.\n * Consumers do not call `trackEvent('page_view', ...)`; registering\n * `automatic: { page_view: {} }` enables the SDK-owned trigger.\n */\nexport const pageViewMetadataSchema = z\n .object({\n page: z\n .object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n })\n .strict(),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .strict()\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n/**\n * Registration config for automatic `page_view`.\n *\n * `page_view` is required in every trigger registry and currently has no\n * options. Use `{ page_view: {} }`.\n */\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for a manually fired `cta_click` event.\n *\n * Use this for non-phone calls to action such as directions, appointment\n * buttons, downloads, or external booking links.\n */\nexport const ctaClickMetadataSchema = z\n .object({\n cta_name: z.string(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n section: z.string().nullable().optional(),\n destination_url: z.string().nullable().optional(),\n })\n .strict();\n\nexport type CtaClickMetadata = z.infer<typeof ctaClickMetadataSchema>;\n\n/**\n * Registration config for `cta_click`.\n *\n * This event is manual-only and currently has no registration options.\n */\nexport const ctaClickConfigSchema = z.object({}).strict();\nexport type CtaClickConfig = z.infer<typeof ctaClickConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Registered trigger names reported by the SDK heartbeat.\n */\nexport const sdkHeartbeatTriggersSchema = z.object({\n automatic: z.array(z.string()),\n manual: z.array(z.string()),\n}).strict();\n\n/**\n * Metadata for the SDK-internal `sdk_heartbeat` event.\n *\n * The SDK fires this once per new session so the dashboard can show which SDK\n * version, install surface, and trigger registry a client site is running.\n * Consumers do not manually register or fire this event.\n */\nexport const sdkHeartbeatMetadataSchema = z\n .object({\n sdk_version: z.string(),\n package_name: z.string().nullable(),\n surface: z.enum(['next', 'react', 'script']),\n triggers: sdkHeartbeatTriggersSchema,\n trigger_config: z.record(z.string(), z.record(z.string(), z.unknown())).nullable().optional(),\n })\n .strict();\n\nexport type SdkHeartbeatMetadata = z.infer<typeof sdkHeartbeatMetadataSchema>;\n\n/**\n * Internal registration config for `sdk_heartbeat`.\n *\n * This event has no consumer-facing options.\n */\nexport const sdkHeartbeatConfigSchema = z.object({}).strict();\nexport type SdkHeartbeatConfig = z.infer<typeof sdkHeartbeatConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `form_start` event.\n *\n * The SDK emits this once per form when the visitor first focuses a field.\n */\nexport const formStartMetadataSchema = z\n .object({\n form: z\n .object({\n id: z.string(),\n action: z.string().nullable(),\n })\n .strict(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type FormStartMetadata = z.infer<typeof formStartMetadataSchema>;\n\n/**\n * Registration config for automatic `form_start`.\n *\n * Use `selector` to narrow which forms can trigger the event. When omitted,\n * the SDK observes all `<form>` elements.\n */\nexport const formStartConfigSchema = z\n .object({\n selector: z.string().optional(),\n })\n .strict();\n\nexport type FormStartConfig = z.infer<typeof formStartConfigSchema>;\n","import { z } from 'zod';\n\n// form_submit is manual — the SDK never auto-fires this. Consumer code calls\n// `tracking.trackEvent('form_submit', { form, page })` from their own submit\n// handler. Registering it enables the type-level permission; omitting it\n// turns manual calls into a compile error.\n//\n// The optional `fields` array captures submitted form field metadata and JSON\n// values. Consumers explicitly build the fields array themselves so they\n// control exactly what is sent.\n\n/**\n * JSON-serializable value accepted by `form_submit.fields[].value`.\n *\n * This intentionally excludes `undefined`, functions, symbols, `Date`\n * instances, and non-finite numbers. Values are stored in PostgreSQL JSONB, so\n * consumers should send only data that has a stable JSON representation.\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number().finite(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(jsonValueSchema),\n ]),\n);\n\n/**\n * Metadata for a manually fired `form_submit` event.\n *\n * Register the event with `manual: { form_submit: {} }`, then call\n * `trackEvent('form_submit', metadata)` from the host site's submit handler.\n *\n * `fields` is optional. If present, each field value must be JSON-serializable\n * and should be explicitly allowlisted by the integration. Do not send names,\n * emails, visitor phone numbers, addresses, payment data, medical details,\n * passwords, file contents, or free-text messages.\n *\n * @example\n * ```ts\n * tracking.trackEvent('form_submit', {\n * form: {\n * id: 'lead-form',\n * action: '/api/lead',\n * fields: [\n * {\n * name: 'service_interest',\n * type: 'select',\n * label: 'Service interest',\n * value: 'teeth_whitening',\n * },\n * ],\n * },\n * page: { path: window.location.pathname },\n * });\n * ```\n */\nexport const formSubmitMetadataSchema = z\n .object({\n form: z\n .object({\n id: z.string(),\n action: z.string().nullable(),\n fields: z\n .array(\n z\n .object({\n name: z.string(),\n type: z.string(),\n label: z.string().nullable(),\n value: jsonValueSchema,\n })\n .strict(),\n )\n .optional(),\n })\n .strict(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type FormSubmitMetadata = z.infer<typeof formSubmitMetadataSchema>;\n\n/**\n * Registration config for `form_submit`.\n *\n * This event is manual-only and currently has no registration options. The\n * empty object enables typed `trackEvent('form_submit', ...)` calls.\n */\nexport const formSubmitConfigSchema = z.object({}).strict();\nexport type FormSubmitConfig = z.infer<typeof formSubmitConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `multi_page_session` event.\n *\n * Fired when the visitor reaches the configured distinct-page threshold in a\n * single tracking session.\n */\nexport const multiPageSessionMetadataSchema = z\n .object({\n page_count: z.number().int().min(2),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type MultiPageSessionMetadata = z.infer<typeof multiPageSessionMetadataSchema>;\n\n/**\n * Registration config for automatic `multi_page_session`.\n */\nexport const multiPageSessionConfigSchema = z\n .object({\n pageThreshold: z.number().int().min(2),\n })\n .strict();\n\nexport type MultiPageSessionConfig = z.infer<typeof multiPageSessionConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for a manually fired `phone_click` event.\n *\n * `phone_number` should be the business phone number from the clicked `tel:`\n * link, not a visitor-entered phone number. `section` can distinguish header,\n * footer, hero, or contact-page links.\n */\nexport const phoneClickMetadataSchema = z\n .object({\n phone_number: z.string(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n section: z.string().nullable().optional(),\n })\n .strict();\n\nexport type PhoneClickMetadata = z.infer<typeof phoneClickMetadataSchema>;\n\n/**\n * Registration config for `phone_click`.\n *\n * This event is manual-only and currently has no registration options.\n */\nexport const phoneClickConfigSchema = z.object({}).strict();\nexport type PhoneClickConfig = z.infer<typeof phoneClickConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `scroll_depth` event.\n *\n * Fired once per configured threshold per page.\n */\nexport const scrollDepthMetadataSchema = z\n .object({\n depth_percent: z.number().int().min(1).max(100),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type ScrollDepthMetadata = z.infer<typeof scrollDepthMetadataSchema>;\n\n/**\n * Registration config for automatic `scroll_depth`.\n *\n * `thresholds` are integer percentages from 1 to 100.\n */\nexport const scrollDepthConfigSchema = z\n .object({\n thresholds: z.array(z.number().int().min(1).max(100)).min(1),\n })\n .strict();\n\nexport type ScrollDepthConfig = z.infer<typeof scrollDepthConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Canonical page intent names supported by `specific_page_visit`.\n */\nexport const SPECIFIC_PAGE_NAMES = [\n 'contact_page',\n 'about_page',\n 'services_page',\n 'booking_page',\n 'location_page',\n 'pricing_page',\n 'faq_page',\n 'testimonials_page',\n] as const;\n\nexport type SpecificPageName = (typeof SPECIFIC_PAGE_NAMES)[number];\n\nexport const specificPageNameSchema = z.enum(SPECIFIC_PAGE_NAMES);\n\n/**\n * Metadata for the automatic `specific_page_visit` event.\n *\n * The SDK emits this when the current pathname matches one of the configured\n * named page patterns.\n */\nexport const specificPageVisitMetadataSchema = z\n .object({\n page_name: specificPageNameSchema,\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type SpecificPageVisitMetadata = z.infer<typeof specificPageVisitMetadataSchema>;\n\n/**\n * Registration config for automatic `specific_page_visit`.\n *\n * Each page entry pairs a semantic `name` with a `RegExp` that matches the\n * pathname. Use this instead of hard-coding path regexes downstream.\n */\nexport const specificPageVisitConfigSchema = z\n .object({\n pages: z\n .array(\n z\n .object({\n name: specificPageNameSchema,\n pathPattern: z.custom<RegExp>((value) => value instanceof RegExp, {\n message: 'pathPattern must be a RegExp',\n }),\n })\n .strict(),\n )\n .min(1),\n })\n .strict();\n\nexport type SpecificPageVisitConfig = z.infer<typeof specificPageVisitConfigSchema>;\n","import { z } from 'zod';\n\n/**\n * Metadata for the automatic `time_on_site` event.\n *\n * The SDK starts a visibility-aware timer and fires once when visible\n * engagement crosses the configured threshold.\n */\nexport const timeOnSiteMetadataSchema = z\n .object({\n duration_ms: z.number().int().nonnegative(),\n page: z\n .object({\n path: z.string(),\n })\n .strict(),\n })\n .strict();\n\nexport type TimeOnSiteMetadata = z.infer<typeof timeOnSiteMetadataSchema>;\n\n/**\n * Registration config for automatic `time_on_site`.\n */\nexport const timeOnSiteConfigSchema = z\n .object({\n thresholdSeconds: z.number().int().positive(),\n })\n .strict();\nexport type TimeOnSiteConfig = z.infer<typeof timeOnSiteConfigSchema>;\n","import type { z } from 'zod';\n\nimport {\n ctaClickConfigSchema,\n ctaClickMetadataSchema,\n type CtaClickConfig,\n type CtaClickMetadata,\n} from './cta-click';\nimport {\n sdkHeartbeatConfigSchema,\n sdkHeartbeatMetadataSchema,\n type SdkHeartbeatConfig,\n type SdkHeartbeatMetadata,\n} from './sdk-heartbeat';\nimport {\n formStartConfigSchema,\n formStartMetadataSchema,\n type FormStartConfig,\n type FormStartMetadata,\n} from './form-start';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\nimport {\n multiPageSessionConfigSchema,\n multiPageSessionMetadataSchema,\n type MultiPageSessionConfig,\n type MultiPageSessionMetadata,\n} from './multi-page-session';\nimport {\n pageViewConfigSchema,\n pageViewMetadataSchema,\n type PageViewConfig,\n type PageViewMetadata,\n} from './page-view';\nimport {\n phoneClickConfigSchema,\n phoneClickMetadataSchema,\n type PhoneClickConfig,\n type PhoneClickMetadata,\n} from './phone-click';\nimport {\n scrollDepthConfigSchema,\n scrollDepthMetadataSchema,\n type ScrollDepthConfig,\n type ScrollDepthMetadata,\n} from './scroll-depth';\nimport {\n specificPageVisitConfigSchema,\n specificPageVisitMetadataSchema,\n type SpecificPageVisitConfig,\n type SpecificPageVisitMetadata,\n} from './specific-page-visit';\nimport {\n timeOnSiteConfigSchema,\n timeOnSiteMetadataSchema,\n type TimeOnSiteConfig,\n type TimeOnSiteMetadata,\n} from './time-on-site';\n\n// Event kind — automatic events are fired by the SDK itself when their\n// client-side signal fires (page_view on navigation, time_on_site on timer,\n// etc). Manual events are only fireable via explicit consumer code.\nexport type EventKind = 'automatic' | 'manual';\n\n// The registry is the single client-side source of truth for \"what events\n// exist, what shape does their metadata take, and what kind are they\". The\n// backend mirror lives in apps/api/src/schemas/tracking_events.py and is\n// kept in sync via the drift test in apps/api/tests/test_event_schema_drift.py.\nexport const EVENT_REGISTRY = {\n // --- automatic triggers ---\n page_view: {\n kind: 'automatic',\n metadataSchema: pageViewMetadataSchema,\n configSchema: pageViewConfigSchema,\n },\n time_on_site: {\n kind: 'automatic',\n metadataSchema: timeOnSiteMetadataSchema,\n configSchema: timeOnSiteConfigSchema,\n },\n specific_page_visit: {\n kind: 'automatic',\n metadataSchema: specificPageVisitMetadataSchema,\n configSchema: specificPageVisitConfigSchema,\n },\n scroll_depth: {\n kind: 'automatic',\n metadataSchema: scrollDepthMetadataSchema,\n configSchema: scrollDepthConfigSchema,\n },\n multi_page_session: {\n kind: 'automatic',\n metadataSchema: multiPageSessionMetadataSchema,\n configSchema: multiPageSessionConfigSchema,\n },\n form_start: {\n kind: 'automatic',\n metadataSchema: formStartMetadataSchema,\n configSchema: formStartConfigSchema,\n },\n // --- SDK-internal automatic (not consumer-configurable) ---\n sdk_heartbeat: {\n kind: 'automatic',\n metadataSchema: sdkHeartbeatMetadataSchema,\n configSchema: sdkHeartbeatConfigSchema,\n },\n // --- manual triggers ---\n form_submit: {\n kind: 'manual',\n metadataSchema: formSubmitMetadataSchema,\n configSchema: formSubmitConfigSchema,\n },\n phone_click: {\n kind: 'manual',\n metadataSchema: phoneClickMetadataSchema,\n configSchema: phoneClickConfigSchema,\n },\n cta_click: {\n kind: 'manual',\n metadataSchema: ctaClickMetadataSchema,\n configSchema: ctaClickConfigSchema,\n },\n} as const;\n\n/**\n * Name of any event known to the tracking SDK.\n */\nexport type EventName = keyof typeof EVENT_REGISTRY;\n\n/**\n * Event names that are fired by the SDK when their configured signal occurs.\n *\n * Automatic events are not accepted by the typed `trackEvent()` API.\n */\nexport type AutomaticEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'automatic' ? K : never;\n}[EventName];\n\n/**\n * Event names that consumer code can fire manually after registering them.\n */\nexport type ManualEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'manual' ? K : never;\n}[EventName];\n\n// Map an event name to its explicit metadata / config TS type. We pull the\n// inferred types directly from the per-event files rather than deriving\n// via `z.infer<typeof EVENT_REGISTRY[K]['metadataSchema']>` so that error\n// messages name the event-specific type (PageViewMetadata, not an\n// anonymous zod inference).\ntype MetadataByName = {\n page_view: PageViewMetadata;\n time_on_site: TimeOnSiteMetadata;\n specific_page_visit: SpecificPageVisitMetadata;\n scroll_depth: ScrollDepthMetadata;\n multi_page_session: MultiPageSessionMetadata;\n form_start: FormStartMetadata;\n sdk_heartbeat: SdkHeartbeatMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n cta_click: CtaClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n specific_page_visit: SpecificPageVisitConfig;\n scroll_depth: ScrollDepthConfig;\n multi_page_session: MultiPageSessionConfig;\n form_start: FormStartConfig;\n sdk_heartbeat: SdkHeartbeatConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\n cta_click: CtaClickConfig;\n};\n\n/**\n * Metadata payload type for a specific tracking event.\n *\n * @example\n * ```ts\n * type SubmitMetadata = EventMetadata<'form_submit'>;\n * ```\n */\nexport type EventMetadata<K extends EventName> = MetadataByName[K];\n\n/**\n * Trigger registration config type for a specific tracking event.\n */\nexport type EventConfig<K extends EventName> = ConfigByName[K];\n\n// Runtime constant arrays for iteration at consumer / factory time.\n/**\n * Runtime list of automatic event names.\n */\nexport const ALL_AUTOMATIC_EVENT_NAMES: readonly AutomaticEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'automatic')\n .map(([name]) => name) as AutomaticEventName[];\n\n/**\n * Runtime list of manual event names.\n */\nexport const ALL_MANUAL_EVENT_NAMES: readonly ManualEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'manual')\n .map(([name]) => name) as ManualEventName[];\n\n/**\n * Trigger registry passed to `createTracking({ triggers })`.\n *\n * `automatic.page_view` is required because every install should capture page\n * views. Other automatic events are opt-in. Manual events must be registered\n * here before the typed client accepts `trackEvent()` calls for them.\n *\n * @example\n * ```ts\n * createTracking({\n * apiKey,\n * endpoint,\n * triggers: {\n * automatic: {\n * page_view: {},\n * time_on_site: { thresholdSeconds: 60 },\n * },\n * manual: {\n * form_submit: {},\n * phone_click: {},\n * },\n * },\n * });\n * ```\n */\nexport type TriggerRegistryConfig = {\n automatic: {\n page_view: EventConfig<'page_view'>;\n } & Partial<{\n time_on_site: EventConfig<'time_on_site'>;\n specific_page_visit: EventConfig<'specific_page_visit'>;\n scroll_depth: EventConfig<'scroll_depth'>;\n multi_page_session: EventConfig<'multi_page_session'>;\n form_start: EventConfig<'form_start'>;\n }>;\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_click'>;\n cta_click: EventConfig<'cta_click'>;\n }>;\n};\n\n/**\n * Manual event names registered in a concrete trigger registry.\n *\n * Used by `TypedTrackingClient` so `trackEvent()` only accepts events the\n * consumer explicitly enabled.\n */\nexport type RegisteredManualEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof NonNullable<TRegistry['manual']>, ManualEventName>;\n\n/**\n * Automatic event names registered in a concrete trigger registry.\n */\nexport type RegisteredAutomaticEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof TRegistry['automatic'], AutomaticEventName>;\n\n/**\n * Discriminated union of valid manual tracking calls for a registry.\n */\nexport type TrackableEvent<TRegistry extends TriggerRegistryConfig> = {\n [K in RegisteredManualEvents<TRegistry>]: {\n eventType: K;\n metadata: EventMetadata<K>;\n };\n}[RegisteredManualEvents<TRegistry>];\n\n// Runtime helper: look up a schema pair by name. Cast through `unknown`\n// because the registry is `as const` and TS loses the specific schema type\n// when indexing via a dynamic key.\nexport function getEventDefinition(name: EventName): {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n} {\n return EVENT_REGISTRY[name] as unknown as {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n };\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z"]}