@aranova/tracking-next 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -0
- package/dist/index.d.mts +47 -43
- package/dist/index.d.ts +47 -43
- package/dist/index.js +26 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +26 -16
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +25 -15
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +25 -15
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.js +25 -15
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +25 -15
- package/dist/server.mjs.map +1 -1
- package/package.json +3 -2
package/dist/middleware.js
CHANGED
|
@@ -57,7 +57,7 @@ var pageViewMetadataSchema = import_zod.z.object({
|
|
|
57
57
|
path: import_zod.z.string(),
|
|
58
58
|
search: import_zod.z.string(),
|
|
59
59
|
hash: import_zod.z.string()
|
|
60
|
-
}),
|
|
60
|
+
}).strict(),
|
|
61
61
|
referrer: import_zod.z.string().nullable(),
|
|
62
62
|
// `.nullable().optional()` — absent (undefined) OR explicit null OR a
|
|
63
63
|
// real viewport object. Mirrors Pydantic's `_Viewport | None = None`
|
|
@@ -65,7 +65,7 @@ var pageViewMetadataSchema = import_zod.z.object({
|
|
|
65
65
|
viewport: import_zod.z.object({
|
|
66
66
|
w: import_zod.z.number(),
|
|
67
67
|
h: import_zod.z.number()
|
|
68
|
-
}).nullable().optional()
|
|
68
|
+
}).strict().nullable().optional()
|
|
69
69
|
}).strict();
|
|
70
70
|
var pageViewConfigSchema = import_zod.z.object({}).strict();
|
|
71
71
|
|
|
@@ -75,7 +75,7 @@ var ctaClickMetadataSchema = import_zod2.z.object({
|
|
|
75
75
|
cta_name: import_zod2.z.string(),
|
|
76
76
|
page: import_zod2.z.object({
|
|
77
77
|
path: import_zod2.z.string()
|
|
78
|
-
}),
|
|
78
|
+
}).strict(),
|
|
79
79
|
section: import_zod2.z.string().nullable().optional(),
|
|
80
80
|
destination_url: import_zod2.z.string().nullable().optional()
|
|
81
81
|
}).strict();
|
|
@@ -102,10 +102,10 @@ var formStartMetadataSchema = import_zod4.z.object({
|
|
|
102
102
|
form: import_zod4.z.object({
|
|
103
103
|
id: import_zod4.z.string(),
|
|
104
104
|
action: import_zod4.z.string().nullable()
|
|
105
|
-
}),
|
|
105
|
+
}).strict(),
|
|
106
106
|
page: import_zod4.z.object({
|
|
107
107
|
path: import_zod4.z.string()
|
|
108
|
-
})
|
|
108
|
+
}).strict()
|
|
109
109
|
}).strict();
|
|
110
110
|
var formStartConfigSchema = import_zod4.z.object({
|
|
111
111
|
selector: import_zod4.z.string().optional()
|
|
@@ -113,6 +113,16 @@ var formStartConfigSchema = import_zod4.z.object({
|
|
|
113
113
|
|
|
114
114
|
// ../tracking-core/src/events/form-submit.ts
|
|
115
115
|
var import_zod5 = require("zod");
|
|
116
|
+
var jsonValueSchema = import_zod5.z.lazy(
|
|
117
|
+
() => import_zod5.z.union([
|
|
118
|
+
import_zod5.z.string(),
|
|
119
|
+
import_zod5.z.number().finite(),
|
|
120
|
+
import_zod5.z.boolean(),
|
|
121
|
+
import_zod5.z.null(),
|
|
122
|
+
import_zod5.z.array(jsonValueSchema),
|
|
123
|
+
import_zod5.z.record(jsonValueSchema)
|
|
124
|
+
])
|
|
125
|
+
);
|
|
116
126
|
var formSubmitMetadataSchema = import_zod5.z.object({
|
|
117
127
|
form: import_zod5.z.object({
|
|
118
128
|
id: import_zod5.z.string(),
|
|
@@ -122,13 +132,13 @@ var formSubmitMetadataSchema = import_zod5.z.object({
|
|
|
122
132
|
name: import_zod5.z.string(),
|
|
123
133
|
type: import_zod5.z.string(),
|
|
124
134
|
label: import_zod5.z.string().nullable(),
|
|
125
|
-
|
|
126
|
-
})
|
|
135
|
+
value: jsonValueSchema
|
|
136
|
+
}).strict()
|
|
127
137
|
).optional()
|
|
128
|
-
}),
|
|
138
|
+
}).strict(),
|
|
129
139
|
page: import_zod5.z.object({
|
|
130
140
|
path: import_zod5.z.string()
|
|
131
|
-
})
|
|
141
|
+
}).strict()
|
|
132
142
|
}).strict();
|
|
133
143
|
var formSubmitConfigSchema = import_zod5.z.object({}).strict();
|
|
134
144
|
|
|
@@ -138,7 +148,7 @@ var multiPageSessionMetadataSchema = import_zod6.z.object({
|
|
|
138
148
|
page_count: import_zod6.z.number().int().min(2),
|
|
139
149
|
page: import_zod6.z.object({
|
|
140
150
|
path: import_zod6.z.string()
|
|
141
|
-
})
|
|
151
|
+
}).strict()
|
|
142
152
|
}).strict();
|
|
143
153
|
var multiPageSessionConfigSchema = import_zod6.z.object({
|
|
144
154
|
pageThreshold: import_zod6.z.number().int().min(2)
|
|
@@ -150,7 +160,7 @@ var phoneClickMetadataSchema = import_zod7.z.object({
|
|
|
150
160
|
phone_number: import_zod7.z.string(),
|
|
151
161
|
page: import_zod7.z.object({
|
|
152
162
|
path: import_zod7.z.string()
|
|
153
|
-
}),
|
|
163
|
+
}).strict(),
|
|
154
164
|
section: import_zod7.z.string().nullable().optional()
|
|
155
165
|
}).strict();
|
|
156
166
|
var phoneClickConfigSchema = import_zod7.z.object({}).strict();
|
|
@@ -161,7 +171,7 @@ var scrollDepthMetadataSchema = import_zod8.z.object({
|
|
|
161
171
|
depth_percent: import_zod8.z.number().int().min(1).max(100),
|
|
162
172
|
page: import_zod8.z.object({
|
|
163
173
|
path: import_zod8.z.string()
|
|
164
|
-
})
|
|
174
|
+
}).strict()
|
|
165
175
|
}).strict();
|
|
166
176
|
var scrollDepthConfigSchema = import_zod8.z.object({
|
|
167
177
|
thresholds: import_zod8.z.array(import_zod8.z.number().int().min(1).max(100)).min(1)
|
|
@@ -184,7 +194,7 @@ var specificPageVisitMetadataSchema = import_zod9.z.object({
|
|
|
184
194
|
page_name: specificPageNameSchema,
|
|
185
195
|
page: import_zod9.z.object({
|
|
186
196
|
path: import_zod9.z.string()
|
|
187
|
-
})
|
|
197
|
+
}).strict()
|
|
188
198
|
}).strict();
|
|
189
199
|
var specificPageVisitConfigSchema = import_zod9.z.object({
|
|
190
200
|
pages: import_zod9.z.array(
|
|
@@ -193,7 +203,7 @@ var specificPageVisitConfigSchema = import_zod9.z.object({
|
|
|
193
203
|
pathPattern: import_zod9.z.custom((value) => value instanceof RegExp, {
|
|
194
204
|
message: "pathPattern must be a RegExp"
|
|
195
205
|
})
|
|
196
|
-
})
|
|
206
|
+
}).strict()
|
|
197
207
|
).min(1)
|
|
198
208
|
}).strict();
|
|
199
209
|
|
|
@@ -203,7 +213,7 @@ var timeOnSiteMetadataSchema = import_zod10.z.object({
|
|
|
203
213
|
duration_ms: import_zod10.z.number().int().nonnegative(),
|
|
204
214
|
page: import_zod10.z.object({
|
|
205
215
|
path: import_zod10.z.string()
|
|
206
|
-
})
|
|
216
|
+
}).strict()
|
|
207
217
|
}).strict();
|
|
208
218
|
var timeOnSiteConfigSchema = import_zod10.z.object({
|
|
209
219
|
thresholdSeconds: import_zod10.z.number().int().positive()
|
package/dist/middleware.js.map
CHANGED
|
@@ -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/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"]}
|
|
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\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// 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\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\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\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\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\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\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\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\n .object({\n path: z.string(),\n })\n .strict(),\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\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\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\n .object({\n path: z.string(),\n })\n .strict(),\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\n .object({\n path: z.string(),\n })\n .strict(),\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\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// 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\n .object({\n path: z.string(),\n })\n .strict(),\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"]}
|
package/dist/middleware.mjs
CHANGED
|
@@ -33,7 +33,7 @@ var pageViewMetadataSchema = z.object({
|
|
|
33
33
|
path: z.string(),
|
|
34
34
|
search: z.string(),
|
|
35
35
|
hash: z.string()
|
|
36
|
-
}),
|
|
36
|
+
}).strict(),
|
|
37
37
|
referrer: z.string().nullable(),
|
|
38
38
|
// `.nullable().optional()` — absent (undefined) OR explicit null OR a
|
|
39
39
|
// real viewport object. Mirrors Pydantic's `_Viewport | None = None`
|
|
@@ -41,7 +41,7 @@ var pageViewMetadataSchema = z.object({
|
|
|
41
41
|
viewport: z.object({
|
|
42
42
|
w: z.number(),
|
|
43
43
|
h: z.number()
|
|
44
|
-
}).nullable().optional()
|
|
44
|
+
}).strict().nullable().optional()
|
|
45
45
|
}).strict();
|
|
46
46
|
var pageViewConfigSchema = z.object({}).strict();
|
|
47
47
|
|
|
@@ -51,7 +51,7 @@ var ctaClickMetadataSchema = z2.object({
|
|
|
51
51
|
cta_name: z2.string(),
|
|
52
52
|
page: z2.object({
|
|
53
53
|
path: z2.string()
|
|
54
|
-
}),
|
|
54
|
+
}).strict(),
|
|
55
55
|
section: z2.string().nullable().optional(),
|
|
56
56
|
destination_url: z2.string().nullable().optional()
|
|
57
57
|
}).strict();
|
|
@@ -78,10 +78,10 @@ var formStartMetadataSchema = z4.object({
|
|
|
78
78
|
form: z4.object({
|
|
79
79
|
id: z4.string(),
|
|
80
80
|
action: z4.string().nullable()
|
|
81
|
-
}),
|
|
81
|
+
}).strict(),
|
|
82
82
|
page: z4.object({
|
|
83
83
|
path: z4.string()
|
|
84
|
-
})
|
|
84
|
+
}).strict()
|
|
85
85
|
}).strict();
|
|
86
86
|
var formStartConfigSchema = z4.object({
|
|
87
87
|
selector: z4.string().optional()
|
|
@@ -89,6 +89,16 @@ var formStartConfigSchema = z4.object({
|
|
|
89
89
|
|
|
90
90
|
// ../tracking-core/src/events/form-submit.ts
|
|
91
91
|
import { z as z5 } from "zod";
|
|
92
|
+
var jsonValueSchema = z5.lazy(
|
|
93
|
+
() => z5.union([
|
|
94
|
+
z5.string(),
|
|
95
|
+
z5.number().finite(),
|
|
96
|
+
z5.boolean(),
|
|
97
|
+
z5.null(),
|
|
98
|
+
z5.array(jsonValueSchema),
|
|
99
|
+
z5.record(jsonValueSchema)
|
|
100
|
+
])
|
|
101
|
+
);
|
|
92
102
|
var formSubmitMetadataSchema = z5.object({
|
|
93
103
|
form: z5.object({
|
|
94
104
|
id: z5.string(),
|
|
@@ -98,13 +108,13 @@ var formSubmitMetadataSchema = z5.object({
|
|
|
98
108
|
name: z5.string(),
|
|
99
109
|
type: z5.string(),
|
|
100
110
|
label: z5.string().nullable(),
|
|
101
|
-
|
|
102
|
-
})
|
|
111
|
+
value: jsonValueSchema
|
|
112
|
+
}).strict()
|
|
103
113
|
).optional()
|
|
104
|
-
}),
|
|
114
|
+
}).strict(),
|
|
105
115
|
page: z5.object({
|
|
106
116
|
path: z5.string()
|
|
107
|
-
})
|
|
117
|
+
}).strict()
|
|
108
118
|
}).strict();
|
|
109
119
|
var formSubmitConfigSchema = z5.object({}).strict();
|
|
110
120
|
|
|
@@ -114,7 +124,7 @@ var multiPageSessionMetadataSchema = z6.object({
|
|
|
114
124
|
page_count: z6.number().int().min(2),
|
|
115
125
|
page: z6.object({
|
|
116
126
|
path: z6.string()
|
|
117
|
-
})
|
|
127
|
+
}).strict()
|
|
118
128
|
}).strict();
|
|
119
129
|
var multiPageSessionConfigSchema = z6.object({
|
|
120
130
|
pageThreshold: z6.number().int().min(2)
|
|
@@ -126,7 +136,7 @@ var phoneClickMetadataSchema = z7.object({
|
|
|
126
136
|
phone_number: z7.string(),
|
|
127
137
|
page: z7.object({
|
|
128
138
|
path: z7.string()
|
|
129
|
-
}),
|
|
139
|
+
}).strict(),
|
|
130
140
|
section: z7.string().nullable().optional()
|
|
131
141
|
}).strict();
|
|
132
142
|
var phoneClickConfigSchema = z7.object({}).strict();
|
|
@@ -137,7 +147,7 @@ var scrollDepthMetadataSchema = z8.object({
|
|
|
137
147
|
depth_percent: z8.number().int().min(1).max(100),
|
|
138
148
|
page: z8.object({
|
|
139
149
|
path: z8.string()
|
|
140
|
-
})
|
|
150
|
+
}).strict()
|
|
141
151
|
}).strict();
|
|
142
152
|
var scrollDepthConfigSchema = z8.object({
|
|
143
153
|
thresholds: z8.array(z8.number().int().min(1).max(100)).min(1)
|
|
@@ -160,7 +170,7 @@ var specificPageVisitMetadataSchema = z9.object({
|
|
|
160
170
|
page_name: specificPageNameSchema,
|
|
161
171
|
page: z9.object({
|
|
162
172
|
path: z9.string()
|
|
163
|
-
})
|
|
173
|
+
}).strict()
|
|
164
174
|
}).strict();
|
|
165
175
|
var specificPageVisitConfigSchema = z9.object({
|
|
166
176
|
pages: z9.array(
|
|
@@ -169,7 +179,7 @@ var specificPageVisitConfigSchema = z9.object({
|
|
|
169
179
|
pathPattern: z9.custom((value) => value instanceof RegExp, {
|
|
170
180
|
message: "pathPattern must be a RegExp"
|
|
171
181
|
})
|
|
172
|
-
})
|
|
182
|
+
}).strict()
|
|
173
183
|
).min(1)
|
|
174
184
|
}).strict();
|
|
175
185
|
|
|
@@ -179,7 +189,7 @@ var timeOnSiteMetadataSchema = z10.object({
|
|
|
179
189
|
duration_ms: z10.number().int().nonnegative(),
|
|
180
190
|
page: z10.object({
|
|
181
191
|
path: z10.string()
|
|
182
|
-
})
|
|
192
|
+
}).strict()
|
|
183
193
|
}).strict();
|
|
184
194
|
var timeOnSiteConfigSchema = z10.object({
|
|
185
195
|
thresholdSeconds: z10.number().int().positive()
|
package/dist/middleware.mjs.map
CHANGED
|
@@ -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/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":["z","z","z","z","z","z","z","z","z"]}
|
|
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\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// 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\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\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\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\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\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\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\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\n .object({\n path: z.string(),\n })\n .strict(),\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\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\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\n .object({\n path: z.string(),\n })\n .strict(),\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\n .object({\n path: z.string(),\n })\n .strict(),\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\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// 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\n .object({\n path: z.string(),\n })\n .strict(),\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"]}
|