@aranova/tracking-next 0.5.0 → 0.5.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.
@@ -81,79 +81,94 @@ var ctaClickMetadataSchema = import_zod2.z.object({
81
81
  }).strict();
82
82
  var ctaClickConfigSchema = import_zod2.z.object({}).strict();
83
83
 
84
- // ../tracking-core/src/events/form-start.ts
84
+ // ../tracking-core/src/events/sdk-heartbeat.ts
85
85
  var import_zod3 = require("zod");
86
- var formStartMetadataSchema = import_zod3.z.object({
87
- form: import_zod3.z.object({
88
- id: import_zod3.z.string(),
89
- action: import_zod3.z.string().nullable()
90
- }),
91
- page: import_zod3.z.object({
92
- path: import_zod3.z.string()
93
- })
86
+ var sdkHeartbeatTriggersSchema = import_zod3.z.object({
87
+ automatic: import_zod3.z.array(import_zod3.z.string()),
88
+ manual: import_zod3.z.array(import_zod3.z.string())
94
89
  }).strict();
95
- var formStartConfigSchema = import_zod3.z.object({
96
- selector: import_zod3.z.string().optional()
90
+ var sdkHeartbeatMetadataSchema = import_zod3.z.object({
91
+ sdk_version: import_zod3.z.string(),
92
+ package_name: import_zod3.z.string().nullable(),
93
+ surface: import_zod3.z.enum(["next", "react", "script"]),
94
+ triggers: sdkHeartbeatTriggersSchema,
95
+ trigger_config: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown())).nullable().optional()
97
96
  }).strict();
97
+ var sdkHeartbeatConfigSchema = import_zod3.z.object({}).strict();
98
98
 
99
- // ../tracking-core/src/events/form-submit.ts
99
+ // ../tracking-core/src/events/form-start.ts
100
100
  var import_zod4 = require("zod");
101
- var formSubmitMetadataSchema = import_zod4.z.object({
101
+ var formStartMetadataSchema = import_zod4.z.object({
102
102
  form: import_zod4.z.object({
103
103
  id: import_zod4.z.string(),
104
- action: import_zod4.z.string().nullable(),
105
- fields: import_zod4.z.array(
106
- import_zod4.z.object({
107
- name: import_zod4.z.string(),
108
- type: import_zod4.z.string(),
109
- label: import_zod4.z.string().nullable(),
110
- has_value: import_zod4.z.boolean()
111
- })
112
- ).optional()
104
+ action: import_zod4.z.string().nullable()
113
105
  }),
114
106
  page: import_zod4.z.object({
115
107
  path: import_zod4.z.string()
116
108
  })
117
109
  }).strict();
118
- var formSubmitConfigSchema = import_zod4.z.object({}).strict();
110
+ var formStartConfigSchema = import_zod4.z.object({
111
+ selector: import_zod4.z.string().optional()
112
+ }).strict();
119
113
 
120
- // ../tracking-core/src/events/multi-page-session.ts
114
+ // ../tracking-core/src/events/form-submit.ts
121
115
  var import_zod5 = require("zod");
122
- var multiPageSessionMetadataSchema = import_zod5.z.object({
123
- page_count: import_zod5.z.number().int().min(2),
116
+ var formSubmitMetadataSchema = import_zod5.z.object({
117
+ form: import_zod5.z.object({
118
+ id: import_zod5.z.string(),
119
+ action: import_zod5.z.string().nullable(),
120
+ fields: import_zod5.z.array(
121
+ import_zod5.z.object({
122
+ name: import_zod5.z.string(),
123
+ type: import_zod5.z.string(),
124
+ label: import_zod5.z.string().nullable(),
125
+ has_value: import_zod5.z.boolean()
126
+ })
127
+ ).optional()
128
+ }),
124
129
  page: import_zod5.z.object({
125
130
  path: import_zod5.z.string()
126
131
  })
127
132
  }).strict();
128
- var multiPageSessionConfigSchema = import_zod5.z.object({
129
- pageThreshold: import_zod5.z.number().int().min(2)
130
- }).strict();
133
+ var formSubmitConfigSchema = import_zod5.z.object({}).strict();
131
134
 
132
- // ../tracking-core/src/events/phone-click.ts
135
+ // ../tracking-core/src/events/multi-page-session.ts
133
136
  var import_zod6 = require("zod");
134
- var phoneClickMetadataSchema = import_zod6.z.object({
135
- phone_number: import_zod6.z.string(),
137
+ var multiPageSessionMetadataSchema = import_zod6.z.object({
138
+ page_count: import_zod6.z.number().int().min(2),
136
139
  page: import_zod6.z.object({
137
140
  path: import_zod6.z.string()
138
- }),
139
- section: import_zod6.z.string().nullable().optional()
141
+ })
142
+ }).strict();
143
+ var multiPageSessionConfigSchema = import_zod6.z.object({
144
+ pageThreshold: import_zod6.z.number().int().min(2)
140
145
  }).strict();
141
- var phoneClickConfigSchema = import_zod6.z.object({}).strict();
142
146
 
143
- // ../tracking-core/src/events/scroll-depth.ts
147
+ // ../tracking-core/src/events/phone-click.ts
144
148
  var import_zod7 = require("zod");
145
- var scrollDepthMetadataSchema = import_zod7.z.object({
146
- depth_percent: import_zod7.z.number().int().min(1).max(100),
149
+ var phoneClickMetadataSchema = import_zod7.z.object({
150
+ phone_number: import_zod7.z.string(),
147
151
  page: import_zod7.z.object({
148
152
  path: import_zod7.z.string()
153
+ }),
154
+ section: import_zod7.z.string().nullable().optional()
155
+ }).strict();
156
+ var phoneClickConfigSchema = import_zod7.z.object({}).strict();
157
+
158
+ // ../tracking-core/src/events/scroll-depth.ts
159
+ var import_zod8 = require("zod");
160
+ var scrollDepthMetadataSchema = import_zod8.z.object({
161
+ depth_percent: import_zod8.z.number().int().min(1).max(100),
162
+ page: import_zod8.z.object({
163
+ path: import_zod8.z.string()
149
164
  })
150
165
  }).strict();
151
- var scrollDepthConfigSchema = import_zod7.z.object({
152
- thresholds: import_zod7.z.array(import_zod7.z.number().int().min(1).max(100)).min(1)
166
+ var scrollDepthConfigSchema = import_zod8.z.object({
167
+ thresholds: import_zod8.z.array(import_zod8.z.number().int().min(1).max(100)).min(1)
153
168
  }).strict();
154
169
 
155
170
  // ../tracking-core/src/events/specific-page-visit.ts
156
- var import_zod8 = require("zod");
171
+ var import_zod9 = require("zod");
157
172
  var SPECIFIC_PAGE_NAMES = [
158
173
  "contact_page",
159
174
  "about_page",
@@ -164,18 +179,18 @@ var SPECIFIC_PAGE_NAMES = [
164
179
  "faq_page",
165
180
  "testimonials_page"
166
181
  ];
167
- var specificPageNameSchema = import_zod8.z.enum(SPECIFIC_PAGE_NAMES);
168
- var specificPageVisitMetadataSchema = import_zod8.z.object({
182
+ var specificPageNameSchema = import_zod9.z.enum(SPECIFIC_PAGE_NAMES);
183
+ var specificPageVisitMetadataSchema = import_zod9.z.object({
169
184
  page_name: specificPageNameSchema,
170
- page: import_zod8.z.object({
171
- path: import_zod8.z.string()
185
+ page: import_zod9.z.object({
186
+ path: import_zod9.z.string()
172
187
  })
173
188
  }).strict();
174
- var specificPageVisitConfigSchema = import_zod8.z.object({
175
- pages: import_zod8.z.array(
176
- import_zod8.z.object({
189
+ var specificPageVisitConfigSchema = import_zod9.z.object({
190
+ pages: import_zod9.z.array(
191
+ import_zod9.z.object({
177
192
  name: specificPageNameSchema,
178
- pathPattern: import_zod8.z.custom((value) => value instanceof RegExp, {
193
+ pathPattern: import_zod9.z.custom((value) => value instanceof RegExp, {
179
194
  message: "pathPattern must be a RegExp"
180
195
  })
181
196
  })
@@ -183,15 +198,15 @@ var specificPageVisitConfigSchema = import_zod8.z.object({
183
198
  }).strict();
184
199
 
185
200
  // ../tracking-core/src/events/time-on-site.ts
186
- var import_zod9 = require("zod");
187
- var timeOnSiteMetadataSchema = import_zod9.z.object({
188
- duration_ms: import_zod9.z.number().int().nonnegative(),
189
- page: import_zod9.z.object({
190
- path: import_zod9.z.string()
201
+ var import_zod10 = require("zod");
202
+ var timeOnSiteMetadataSchema = import_zod10.z.object({
203
+ duration_ms: import_zod10.z.number().int().nonnegative(),
204
+ page: import_zod10.z.object({
205
+ path: import_zod10.z.string()
191
206
  })
192
207
  }).strict();
193
- var timeOnSiteConfigSchema = import_zod9.z.object({
194
- thresholdSeconds: import_zod9.z.number().int().positive()
208
+ var timeOnSiteConfigSchema = import_zod10.z.object({
209
+ thresholdSeconds: import_zod10.z.number().int().positive()
195
210
  }).strict();
196
211
 
197
212
  // ../tracking-core/src/events/registry.ts
@@ -227,6 +242,12 @@ var EVENT_REGISTRY = {
227
242
  metadataSchema: formStartMetadataSchema,
228
243
  configSchema: formStartConfigSchema
229
244
  },
245
+ // --- SDK-internal automatic (not consumer-configurable) ---
246
+ sdk_heartbeat: {
247
+ kind: "automatic",
248
+ metadataSchema: sdkHeartbeatMetadataSchema,
249
+ configSchema: sdkHeartbeatConfigSchema
250
+ },
230
251
  // --- manual triggers ---
231
252
  form_submit: {
232
253
  kind: "manual",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.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/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 { NextResponse } from 'next/server';\nimport type { NextMiddleware, NextRequest } from 'next/server';\n\nimport { getTrackingQueryValues } from '../../tracking-core/src/index';\n\nexport function createTrackingMiddleware(): NextMiddleware {\n return (request: NextRequest) => {\n const response = NextResponse.next();\n const trackingValues = getTrackingQueryValues(request.nextUrl.searchParams);\n\n Object.entries(trackingValues).forEach(([param, value]) => {\n response.cookies.set(param, value, {\n maxAge: 7776000,\n path: '/',\n httpOnly: false,\n });\n });\n\n return response;\n };\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return parsed.id;\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return fresh.id;\n}\n\n/** Test/debug helper — wipes both ids. */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","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// 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 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 // --- 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 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 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"]}
1
+ {"version":3,"sources":["../src/middleware.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 { NextResponse } from 'next/server';\nimport type { NextMiddleware, NextRequest } from 'next/server';\n\nimport { getTrackingQueryValues } from '../../tracking-core/src/index';\n\nexport function createTrackingMiddleware(): NextMiddleware {\n return (request: NextRequest) => {\n const response = NextResponse.next();\n const trackingValues = getTrackingQueryValues(request.nextUrl.searchParams);\n\n Object.entries(trackingValues).forEach(([param, value]) => {\n response.cookies.set(param, value, {\n maxAge: 7776000,\n path: '/',\n httpOnly: false,\n });\n });\n\n return response;\n };\n}\n","import 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"]}
@@ -57,79 +57,94 @@ var ctaClickMetadataSchema = z2.object({
57
57
  }).strict();
58
58
  var ctaClickConfigSchema = z2.object({}).strict();
59
59
 
60
- // ../tracking-core/src/events/form-start.ts
60
+ // ../tracking-core/src/events/sdk-heartbeat.ts
61
61
  import { z as z3 } from "zod";
62
- var formStartMetadataSchema = z3.object({
63
- form: z3.object({
64
- id: z3.string(),
65
- action: z3.string().nullable()
66
- }),
67
- page: z3.object({
68
- path: z3.string()
69
- })
62
+ var sdkHeartbeatTriggersSchema = z3.object({
63
+ automatic: z3.array(z3.string()),
64
+ manual: z3.array(z3.string())
70
65
  }).strict();
71
- var formStartConfigSchema = z3.object({
72
- selector: z3.string().optional()
66
+ var sdkHeartbeatMetadataSchema = z3.object({
67
+ sdk_version: z3.string(),
68
+ package_name: z3.string().nullable(),
69
+ surface: z3.enum(["next", "react", "script"]),
70
+ triggers: sdkHeartbeatTriggersSchema,
71
+ trigger_config: z3.record(z3.string(), z3.record(z3.string(), z3.unknown())).nullable().optional()
73
72
  }).strict();
73
+ var sdkHeartbeatConfigSchema = z3.object({}).strict();
74
74
 
75
- // ../tracking-core/src/events/form-submit.ts
75
+ // ../tracking-core/src/events/form-start.ts
76
76
  import { z as z4 } from "zod";
77
- var formSubmitMetadataSchema = z4.object({
77
+ var formStartMetadataSchema = z4.object({
78
78
  form: z4.object({
79
79
  id: z4.string(),
80
- action: z4.string().nullable(),
81
- fields: z4.array(
82
- z4.object({
83
- name: z4.string(),
84
- type: z4.string(),
85
- label: z4.string().nullable(),
86
- has_value: z4.boolean()
87
- })
88
- ).optional()
80
+ action: z4.string().nullable()
89
81
  }),
90
82
  page: z4.object({
91
83
  path: z4.string()
92
84
  })
93
85
  }).strict();
94
- var formSubmitConfigSchema = z4.object({}).strict();
86
+ var formStartConfigSchema = z4.object({
87
+ selector: z4.string().optional()
88
+ }).strict();
95
89
 
96
- // ../tracking-core/src/events/multi-page-session.ts
90
+ // ../tracking-core/src/events/form-submit.ts
97
91
  import { z as z5 } from "zod";
98
- var multiPageSessionMetadataSchema = z5.object({
99
- page_count: z5.number().int().min(2),
92
+ var formSubmitMetadataSchema = z5.object({
93
+ form: z5.object({
94
+ id: z5.string(),
95
+ action: z5.string().nullable(),
96
+ fields: z5.array(
97
+ z5.object({
98
+ name: z5.string(),
99
+ type: z5.string(),
100
+ label: z5.string().nullable(),
101
+ has_value: z5.boolean()
102
+ })
103
+ ).optional()
104
+ }),
100
105
  page: z5.object({
101
106
  path: z5.string()
102
107
  })
103
108
  }).strict();
104
- var multiPageSessionConfigSchema = z5.object({
105
- pageThreshold: z5.number().int().min(2)
106
- }).strict();
109
+ var formSubmitConfigSchema = z5.object({}).strict();
107
110
 
108
- // ../tracking-core/src/events/phone-click.ts
111
+ // ../tracking-core/src/events/multi-page-session.ts
109
112
  import { z as z6 } from "zod";
110
- var phoneClickMetadataSchema = z6.object({
111
- phone_number: z6.string(),
113
+ var multiPageSessionMetadataSchema = z6.object({
114
+ page_count: z6.number().int().min(2),
112
115
  page: z6.object({
113
116
  path: z6.string()
114
- }),
115
- section: z6.string().nullable().optional()
117
+ })
118
+ }).strict();
119
+ var multiPageSessionConfigSchema = z6.object({
120
+ pageThreshold: z6.number().int().min(2)
116
121
  }).strict();
117
- var phoneClickConfigSchema = z6.object({}).strict();
118
122
 
119
- // ../tracking-core/src/events/scroll-depth.ts
123
+ // ../tracking-core/src/events/phone-click.ts
120
124
  import { z as z7 } from "zod";
121
- var scrollDepthMetadataSchema = z7.object({
122
- depth_percent: z7.number().int().min(1).max(100),
125
+ var phoneClickMetadataSchema = z7.object({
126
+ phone_number: z7.string(),
123
127
  page: z7.object({
124
128
  path: z7.string()
129
+ }),
130
+ section: z7.string().nullable().optional()
131
+ }).strict();
132
+ var phoneClickConfigSchema = z7.object({}).strict();
133
+
134
+ // ../tracking-core/src/events/scroll-depth.ts
135
+ import { z as z8 } from "zod";
136
+ var scrollDepthMetadataSchema = z8.object({
137
+ depth_percent: z8.number().int().min(1).max(100),
138
+ page: z8.object({
139
+ path: z8.string()
125
140
  })
126
141
  }).strict();
127
- var scrollDepthConfigSchema = z7.object({
128
- thresholds: z7.array(z7.number().int().min(1).max(100)).min(1)
142
+ var scrollDepthConfigSchema = z8.object({
143
+ thresholds: z8.array(z8.number().int().min(1).max(100)).min(1)
129
144
  }).strict();
130
145
 
131
146
  // ../tracking-core/src/events/specific-page-visit.ts
132
- import { z as z8 } from "zod";
147
+ import { z as z9 } from "zod";
133
148
  var SPECIFIC_PAGE_NAMES = [
134
149
  "contact_page",
135
150
  "about_page",
@@ -140,18 +155,18 @@ var SPECIFIC_PAGE_NAMES = [
140
155
  "faq_page",
141
156
  "testimonials_page"
142
157
  ];
143
- var specificPageNameSchema = z8.enum(SPECIFIC_PAGE_NAMES);
144
- var specificPageVisitMetadataSchema = z8.object({
158
+ var specificPageNameSchema = z9.enum(SPECIFIC_PAGE_NAMES);
159
+ var specificPageVisitMetadataSchema = z9.object({
145
160
  page_name: specificPageNameSchema,
146
- page: z8.object({
147
- path: z8.string()
161
+ page: z9.object({
162
+ path: z9.string()
148
163
  })
149
164
  }).strict();
150
- var specificPageVisitConfigSchema = z8.object({
151
- pages: z8.array(
152
- z8.object({
165
+ var specificPageVisitConfigSchema = z9.object({
166
+ pages: z9.array(
167
+ z9.object({
153
168
  name: specificPageNameSchema,
154
- pathPattern: z8.custom((value) => value instanceof RegExp, {
169
+ pathPattern: z9.custom((value) => value instanceof RegExp, {
155
170
  message: "pathPattern must be a RegExp"
156
171
  })
157
172
  })
@@ -159,15 +174,15 @@ var specificPageVisitConfigSchema = z8.object({
159
174
  }).strict();
160
175
 
161
176
  // ../tracking-core/src/events/time-on-site.ts
162
- import { z as z9 } from "zod";
163
- var timeOnSiteMetadataSchema = z9.object({
164
- duration_ms: z9.number().int().nonnegative(),
165
- page: z9.object({
166
- path: z9.string()
177
+ import { z as z10 } from "zod";
178
+ var timeOnSiteMetadataSchema = z10.object({
179
+ duration_ms: z10.number().int().nonnegative(),
180
+ page: z10.object({
181
+ path: z10.string()
167
182
  })
168
183
  }).strict();
169
- var timeOnSiteConfigSchema = z9.object({
170
- thresholdSeconds: z9.number().int().positive()
184
+ var timeOnSiteConfigSchema = z10.object({
185
+ thresholdSeconds: z10.number().int().positive()
171
186
  }).strict();
172
187
 
173
188
  // ../tracking-core/src/events/registry.ts
@@ -203,6 +218,12 @@ var EVENT_REGISTRY = {
203
218
  metadataSchema: formStartMetadataSchema,
204
219
  configSchema: formStartConfigSchema
205
220
  },
221
+ // --- SDK-internal automatic (not consumer-configurable) ---
222
+ sdk_heartbeat: {
223
+ kind: "automatic",
224
+ metadataSchema: sdkHeartbeatMetadataSchema,
225
+ configSchema: sdkHeartbeatConfigSchema
226
+ },
206
227
  // --- manual triggers ---
207
228
  form_submit: {
208
229
  kind: "manual",