@aranova/tracking-next 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +422 -56
- package/dist/index.d.ts +422 -56
- package/dist/index.js +346 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +346 -42
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +119 -30
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +119 -30
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.js +119 -30
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +119 -30
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/middleware.js
CHANGED
|
@@ -69,26 +69,21 @@ var pageViewMetadataSchema = import_zod.z.object({
|
|
|
69
69
|
}).strict();
|
|
70
70
|
var pageViewConfigSchema = import_zod.z.object({}).strict();
|
|
71
71
|
|
|
72
|
-
// ../tracking-core/src/events/
|
|
72
|
+
// ../tracking-core/src/events/cta-click.ts
|
|
73
73
|
var import_zod2 = require("zod");
|
|
74
|
-
var
|
|
74
|
+
var ctaClickMetadataSchema = import_zod2.z.object({
|
|
75
|
+
cta_name: import_zod2.z.string(),
|
|
75
76
|
page: import_zod2.z.object({
|
|
76
77
|
path: import_zod2.z.string()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
|
|
81
|
-
// real regex at createTracking() time; the factory validates with this
|
|
82
|
-
// schema and then keeps the live RegExp reference for runtime matching.
|
|
83
|
-
pathPattern: import_zod2.z.custom(
|
|
84
|
-
(value) => value instanceof RegExp,
|
|
85
|
-
{ message: "pathPattern must be a RegExp" }
|
|
86
|
-
)
|
|
78
|
+
}),
|
|
79
|
+
section: import_zod2.z.string().nullable().optional(),
|
|
80
|
+
destination_url: import_zod2.z.string().nullable().optional()
|
|
87
81
|
}).strict();
|
|
82
|
+
var ctaClickConfigSchema = import_zod2.z.object({}).strict();
|
|
88
83
|
|
|
89
|
-
// ../tracking-core/src/events/form-
|
|
84
|
+
// ../tracking-core/src/events/form-start.ts
|
|
90
85
|
var import_zod3 = require("zod");
|
|
91
|
-
var
|
|
86
|
+
var formStartMetadataSchema = import_zod3.z.object({
|
|
92
87
|
form: import_zod3.z.object({
|
|
93
88
|
id: import_zod3.z.string(),
|
|
94
89
|
action: import_zod3.z.string().nullable()
|
|
@@ -97,32 +92,106 @@ var formSubmitMetadataSchema = import_zod3.z.object({
|
|
|
97
92
|
path: import_zod3.z.string()
|
|
98
93
|
})
|
|
99
94
|
}).strict();
|
|
100
|
-
var
|
|
95
|
+
var formStartConfigSchema = import_zod3.z.object({
|
|
96
|
+
selector: import_zod3.z.string().optional()
|
|
97
|
+
}).strict();
|
|
101
98
|
|
|
102
|
-
// ../tracking-core/src/events/
|
|
99
|
+
// ../tracking-core/src/events/form-submit.ts
|
|
103
100
|
var import_zod4 = require("zod");
|
|
104
|
-
var
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
var formSubmitMetadataSchema = import_zod4.z.object({
|
|
102
|
+
form: import_zod4.z.object({
|
|
103
|
+
id: import_zod4.z.string(),
|
|
104
|
+
action: import_zod4.z.string().nullable(),
|
|
105
|
+
fields: import_zod4.z.array(
|
|
106
|
+
import_zod4.z.object({
|
|
107
|
+
name: import_zod4.z.string(),
|
|
108
|
+
type: import_zod4.z.string(),
|
|
109
|
+
label: import_zod4.z.string().nullable(),
|
|
110
|
+
has_value: import_zod4.z.boolean()
|
|
111
|
+
})
|
|
112
|
+
).optional()
|
|
109
113
|
}),
|
|
110
114
|
page: import_zod4.z.object({
|
|
111
115
|
path: import_zod4.z.string()
|
|
112
116
|
})
|
|
113
117
|
}).strict();
|
|
114
|
-
var
|
|
118
|
+
var formSubmitConfigSchema = import_zod4.z.object({}).strict();
|
|
115
119
|
|
|
116
|
-
// ../tracking-core/src/events/
|
|
120
|
+
// ../tracking-core/src/events/multi-page-session.ts
|
|
117
121
|
var import_zod5 = require("zod");
|
|
118
|
-
var
|
|
119
|
-
|
|
122
|
+
var multiPageSessionMetadataSchema = import_zod5.z.object({
|
|
123
|
+
page_count: import_zod5.z.number().int().min(2),
|
|
120
124
|
page: import_zod5.z.object({
|
|
121
125
|
path: import_zod5.z.string()
|
|
122
126
|
})
|
|
123
127
|
}).strict();
|
|
124
|
-
var
|
|
125
|
-
|
|
128
|
+
var multiPageSessionConfigSchema = import_zod5.z.object({
|
|
129
|
+
pageThreshold: import_zod5.z.number().int().min(2)
|
|
130
|
+
}).strict();
|
|
131
|
+
|
|
132
|
+
// ../tracking-core/src/events/phone-click.ts
|
|
133
|
+
var import_zod6 = require("zod");
|
|
134
|
+
var phoneClickMetadataSchema = import_zod6.z.object({
|
|
135
|
+
phone_number: import_zod6.z.string(),
|
|
136
|
+
page: import_zod6.z.object({
|
|
137
|
+
path: import_zod6.z.string()
|
|
138
|
+
}),
|
|
139
|
+
section: import_zod6.z.string().nullable().optional()
|
|
140
|
+
}).strict();
|
|
141
|
+
var phoneClickConfigSchema = import_zod6.z.object({}).strict();
|
|
142
|
+
|
|
143
|
+
// ../tracking-core/src/events/scroll-depth.ts
|
|
144
|
+
var import_zod7 = require("zod");
|
|
145
|
+
var scrollDepthMetadataSchema = import_zod7.z.object({
|
|
146
|
+
depth_percent: import_zod7.z.number().int().min(1).max(100),
|
|
147
|
+
page: import_zod7.z.object({
|
|
148
|
+
path: import_zod7.z.string()
|
|
149
|
+
})
|
|
150
|
+
}).strict();
|
|
151
|
+
var scrollDepthConfigSchema = import_zod7.z.object({
|
|
152
|
+
thresholds: import_zod7.z.array(import_zod7.z.number().int().min(1).max(100)).min(1)
|
|
153
|
+
}).strict();
|
|
154
|
+
|
|
155
|
+
// ../tracking-core/src/events/specific-page-visit.ts
|
|
156
|
+
var import_zod8 = require("zod");
|
|
157
|
+
var SPECIFIC_PAGE_NAMES = [
|
|
158
|
+
"contact_page",
|
|
159
|
+
"about_page",
|
|
160
|
+
"services_page",
|
|
161
|
+
"booking_page",
|
|
162
|
+
"location_page",
|
|
163
|
+
"pricing_page",
|
|
164
|
+
"faq_page",
|
|
165
|
+
"testimonials_page"
|
|
166
|
+
];
|
|
167
|
+
var specificPageNameSchema = import_zod8.z.enum(SPECIFIC_PAGE_NAMES);
|
|
168
|
+
var specificPageVisitMetadataSchema = import_zod8.z.object({
|
|
169
|
+
page_name: specificPageNameSchema,
|
|
170
|
+
page: import_zod8.z.object({
|
|
171
|
+
path: import_zod8.z.string()
|
|
172
|
+
})
|
|
173
|
+
}).strict();
|
|
174
|
+
var specificPageVisitConfigSchema = import_zod8.z.object({
|
|
175
|
+
pages: import_zod8.z.array(
|
|
176
|
+
import_zod8.z.object({
|
|
177
|
+
name: specificPageNameSchema,
|
|
178
|
+
pathPattern: import_zod8.z.custom((value) => value instanceof RegExp, {
|
|
179
|
+
message: "pathPattern must be a RegExp"
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
).min(1)
|
|
183
|
+
}).strict();
|
|
184
|
+
|
|
185
|
+
// ../tracking-core/src/events/time-on-site.ts
|
|
186
|
+
var import_zod9 = require("zod");
|
|
187
|
+
var timeOnSiteMetadataSchema = import_zod9.z.object({
|
|
188
|
+
duration_ms: import_zod9.z.number().int().nonnegative(),
|
|
189
|
+
page: import_zod9.z.object({
|
|
190
|
+
path: import_zod9.z.string()
|
|
191
|
+
})
|
|
192
|
+
}).strict();
|
|
193
|
+
var timeOnSiteConfigSchema = import_zod9.z.object({
|
|
194
|
+
thresholdSeconds: import_zod9.z.number().int().positive()
|
|
126
195
|
}).strict();
|
|
127
196
|
|
|
128
197
|
// ../tracking-core/src/events/registry.ts
|
|
@@ -138,10 +207,25 @@ var EVENT_REGISTRY = {
|
|
|
138
207
|
metadataSchema: timeOnSiteMetadataSchema,
|
|
139
208
|
configSchema: timeOnSiteConfigSchema
|
|
140
209
|
},
|
|
141
|
-
|
|
210
|
+
specific_page_visit: {
|
|
142
211
|
kind: "automatic",
|
|
143
|
-
metadataSchema:
|
|
144
|
-
configSchema:
|
|
212
|
+
metadataSchema: specificPageVisitMetadataSchema,
|
|
213
|
+
configSchema: specificPageVisitConfigSchema
|
|
214
|
+
},
|
|
215
|
+
scroll_depth: {
|
|
216
|
+
kind: "automatic",
|
|
217
|
+
metadataSchema: scrollDepthMetadataSchema,
|
|
218
|
+
configSchema: scrollDepthConfigSchema
|
|
219
|
+
},
|
|
220
|
+
multi_page_session: {
|
|
221
|
+
kind: "automatic",
|
|
222
|
+
metadataSchema: multiPageSessionMetadataSchema,
|
|
223
|
+
configSchema: multiPageSessionConfigSchema
|
|
224
|
+
},
|
|
225
|
+
form_start: {
|
|
226
|
+
kind: "automatic",
|
|
227
|
+
metadataSchema: formStartMetadataSchema,
|
|
228
|
+
configSchema: formStartConfigSchema
|
|
145
229
|
},
|
|
146
230
|
// --- manual triggers ---
|
|
147
231
|
form_submit: {
|
|
@@ -153,6 +237,11 @@ var EVENT_REGISTRY = {
|
|
|
153
237
|
kind: "manual",
|
|
154
238
|
metadataSchema: phoneClickMetadataSchema,
|
|
155
239
|
configSchema: phoneClickConfigSchema
|
|
240
|
+
},
|
|
241
|
+
cta_click: {
|
|
242
|
+
kind: "manual",
|
|
243
|
+
metadataSchema: ctaClickMetadataSchema,
|
|
244
|
+
configSchema: ctaClickConfigSchema
|
|
156
245
|
}
|
|
157
246
|
};
|
|
158
247
|
var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
|
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/contact-page-visit.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts"],"sourcesContent":["import 'server-only';\n\nimport { NextResponse } from 'next/server';\nimport type { NextMiddleware, NextRequest } from 'next/server';\n\nimport { getTrackingQueryValues } from '../../tracking-core/src/index';\n\nexport function createTrackingMiddleware(): NextMiddleware {\n return (request: NextRequest) => {\n const response = NextResponse.next();\n const trackingValues = getTrackingQueryValues(request.nextUrl.searchParams);\n\n Object.entries(trackingValues).forEach(([param, value]) => {\n response.cookies.set(param, value, {\n maxAge: 7776000,\n path: '/',\n httpOnly: false,\n });\n });\n\n return response;\n };\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return parsed.id;\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return fresh.id;\n}\n\n/** Test/debug helper — wipes both ids. */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","import { z } from 'zod';\n\n// page_view is automatic and auto-emitted by attachAutoPageView() on initial\n// load, SPA route changes (pushState / replaceState / popstate), and bfcache\n// restore (pageshow with event.persisted === true).\n\nexport const pageViewMetadataSchema = z\n .object({\n page: z.object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n }),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n// No configurable options today, but must be registered for the SDK to attach\n// the auto-page-view handler. Reserved for future options like `throttleMs`\n// or `includeHash`.\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n// contact_page_visit is automatic. The SDK observes every page_view fire and\n// emits a second contact_page_visit event whenever the current pathname\n// matches the consumer-provided `pathPattern`. Lets clients track \"did a\n// visitor reach our contact/booking page\" without writing a listener\n// themselves. pathPattern is a client-side-only RegExp so it isn't shipped\n// over the wire — only the matched metadata is.\n\nexport const contactPageVisitMetadataSchema = z\n .object({\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type ContactPageVisitMetadata = z.infer<typeof contactPageVisitMetadataSchema>;\n\nexport const contactPageVisitConfigSchema = z\n .object({\n // RegExp is runtime-only so we wrap it via z.custom. Consumers pass a\n // real regex at createTracking() time; the factory validates with this\n // schema and then keeps the live RegExp reference for runtime matching.\n pathPattern: z.custom<RegExp>(\n (value) => value instanceof RegExp,\n { message: 'pathPattern must be a RegExp' },\n ),\n })\n .strict();\nexport type ContactPageVisitConfig = z.infer<typeof contactPageVisitConfigSchema>;\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\nexport const formSubmitMetadataSchema = 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 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// phone_click is manual. Consumer code calls trackEvent('phone_click', ...)\n// from a click handler on a tel: anchor or similar. Registering it enables\n// the type-level permission to fire the event.\n\nexport const phoneClickMetadataSchema = z\n .object({\n element: z.object({\n tag: z.string(),\n text: z.string().nullable(),\n href: z.string(),\n }),\n page: z.object({\n path: z.string(),\n }),\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// 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 contactPageVisitConfigSchema,\n contactPageVisitMetadataSchema,\n type ContactPageVisitConfig,\n type ContactPageVisitMetadata,\n} from './contact-page-visit';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\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 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 contact_page_visit: {\n kind: 'automatic',\n metadataSchema: contactPageVisitMetadataSchema,\n configSchema: contactPageVisitConfigSchema,\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} 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 contact_page_visit: ContactPageVisitMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n contact_page_visit: ContactPageVisitConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\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 contact_page_visit: EventConfig<'contact_page_visit'>;\n }>;\n // `manual` is itself optional — most consumers will only fire automatic\n // events. Omitting this property is the same as `manual: {}` (no\n // manually-fireable events registered).\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_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"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/cta-click.ts","../../tracking-core/src/events/form-start.ts","../../tracking-core/src/events/form-submit.ts","../../tracking-core/src/events/multi-page-session.ts","../../tracking-core/src/events/phone-click.ts","../../tracking-core/src/events/scroll-depth.ts","../../tracking-core/src/events/specific-page-visit.ts","../../tracking-core/src/events/time-on-site.ts","../../tracking-core/src/events/registry.ts"],"sourcesContent":["import 'server-only';\n\nimport { NextResponse } from 'next/server';\nimport type { NextMiddleware, NextRequest } from 'next/server';\n\nimport { getTrackingQueryValues } from '../../tracking-core/src/index';\n\nexport function createTrackingMiddleware(): NextMiddleware {\n return (request: NextRequest) => {\n const response = NextResponse.next();\n const trackingValues = getTrackingQueryValues(request.nextUrl.searchParams);\n\n Object.entries(trackingValues).forEach(([param, value]) => {\n response.cookies.set(param, value, {\n maxAge: 7776000,\n path: '/',\n httpOnly: false,\n });\n });\n\n return response;\n };\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return parsed.id;\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return fresh.id;\n}\n\n/** Test/debug helper — wipes both ids. */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","import { z } from 'zod';\n\n// page_view is automatic and auto-emitted by attachAutoPageView() on initial\n// load, SPA route changes (pushState / replaceState / popstate), and bfcache\n// restore (pageshow with event.persisted === true).\n\nexport const pageViewMetadataSchema = z\n .object({\n page: z.object({\n title: z.string().nullable(),\n path: z.string(),\n search: z.string(),\n hash: z.string(),\n }),\n referrer: z.string().nullable(),\n // `.nullable().optional()` — absent (undefined) OR explicit null OR a\n // real viewport object. Mirrors Pydantic's `_Viewport | None = None`\n // on the backend side so the drift test stays clean.\n viewport: z\n .object({\n w: z.number(),\n h: z.number(),\n })\n .nullable()\n .optional(),\n })\n .strict();\n\nexport type PageViewMetadata = z.infer<typeof pageViewMetadataSchema>;\n\n// No configurable options today, but must be registered for the SDK to attach\n// the auto-page-view handler. Reserved for future options like `throttleMs`\n// or `includeHash`.\nexport const pageViewConfigSchema = z.object({}).strict();\nexport type PageViewConfig = z.infer<typeof pageViewConfigSchema>;\n","import { z } from 'zod';\n\n// cta_click is manual. Consumer code calls trackEvent('cta_click', ...) from\n// click handlers on CTAs like \"Get Directions\", \"Book Appointment\", etc.\n// The cta_name is a free-form string chosen by the consumer to identify the\n// action (e.g. \"get_directions\", \"book_appointment\", \"download_brochure\").\n\nexport const ctaClickMetadataSchema = z\n .object({\n cta_name: z.string(),\n page: z.object({\n path: z.string(),\n }),\n section: z.string().nullable().optional(),\n destination_url: z.string().nullable().optional(),\n })\n .strict();\n\nexport type CtaClickMetadata = z.infer<typeof ctaClickMetadataSchema>;\n\nexport const ctaClickConfigSchema = z.object({}).strict();\nexport type CtaClickConfig = z.infer<typeof ctaClickConfigSchema>;\n","import { z } from 'zod';\n\n// form_start is automatic. The SDK uses focusin event delegation on document\n// to detect the first field interaction inside a <form>. Fires once per form\n// per page lifecycle. Resets on SPA navigation.\n\nexport const formStartMetadataSchema = z\n .object({\n form: z.object({\n id: z.string(),\n action: z.string().nullable(),\n }),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type FormStartMetadata = z.infer<typeof formStartMetadataSchema>;\n\nexport const formStartConfigSchema = z\n .object({\n selector: z.string().optional(),\n })\n .strict();\n\nexport type FormStartConfig = z.infer<typeof formStartConfigSchema>;\n","import { z } from 'zod';\n\n// form_submit is manual — the SDK never auto-fires this. Consumer code calls\n// `tracking.trackEvent('form_submit', { form, page })` from their own submit\n// handler. Registering it enables the type-level permission; omitting it\n// turns manual calls into a compile error.\n//\n// The optional `fields` array captures structural metadata about the form\n// fields (name, type, label, whether the field was filled). It NEVER captures\n// actual field values — this is a hard PII boundary. Consumers explicitly\n// build the fields array themselves so they control exactly what is sent.\n\nexport const formSubmitMetadataSchema = z\n .object({\n form: z.object({\n id: z.string(),\n action: z.string().nullable(),\n fields: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n label: z.string().nullable(),\n has_value: z.boolean(),\n }),\n )\n .optional(),\n }),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type FormSubmitMetadata = z.infer<typeof formSubmitMetadataSchema>;\n\nexport const formSubmitConfigSchema = z.object({}).strict();\nexport type FormSubmitConfig = z.infer<typeof formSubmitConfigSchema>;\n","import { z } from 'zod';\n\n// multi_page_session is automatic. The SDK tracks distinct page paths visited\n// in the current session via sessionStorage. When the count reaches the\n// configured threshold, fires once. Resets on session rotation.\n\nexport const multiPageSessionMetadataSchema = z\n .object({\n page_count: z.number().int().min(2),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type MultiPageSessionMetadata = z.infer<typeof multiPageSessionMetadataSchema>;\n\nexport const multiPageSessionConfigSchema = z\n .object({\n pageThreshold: z.number().int().min(2),\n })\n .strict();\n\nexport type MultiPageSessionConfig = z.infer<typeof multiPageSessionConfigSchema>;\n","import { z } from 'zod';\n\n// phone_click is manual. Consumer code calls trackEvent('phone_click', ...)\n// from a click handler on a tel: anchor or similar. The phone_number is the\n// business's number (extracted from the href), not the visitor's. The optional\n// section helps distinguish multiple phone links on the same page.\n\nexport const phoneClickMetadataSchema = z\n .object({\n phone_number: z.string(),\n page: z.object({\n path: z.string(),\n }),\n section: z.string().nullable().optional(),\n })\n .strict();\n\nexport type PhoneClickMetadata = z.infer<typeof phoneClickMetadataSchema>;\n\nexport const phoneClickConfigSchema = z.object({}).strict();\nexport type PhoneClickConfig = z.infer<typeof phoneClickConfigSchema>;\n","import { z } from 'zod';\n\n// scroll_depth is automatic. The SDK installs a requestAnimationFrame-throttled\n// scroll listener and fires once per configured threshold per page. Resets on\n// SPA navigation.\n\nexport const scrollDepthMetadataSchema = z\n .object({\n depth_percent: z.number().int().min(1).max(100),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type ScrollDepthMetadata = z.infer<typeof scrollDepthMetadataSchema>;\n\nexport const scrollDepthConfigSchema = z\n .object({\n thresholds: z.array(z.number().int().min(1).max(100)).min(1),\n })\n .strict();\n\nexport type ScrollDepthConfig = z.infer<typeof scrollDepthConfigSchema>;\n","import { z } from 'zod';\n\n// specific_page_visit is automatic. The SDK observes every navigation and\n// emits a specific_page_visit event when the current pathname matches one of\n// the consumer-provided patterns. Each pattern has a strongly typed name\n// (contact_page, services_page, etc.) so the event stream is self-describing\n// and downstream analytics can group by page intent without re-applying regexes.\n\nexport const SPECIFIC_PAGE_NAMES = [\n 'contact_page',\n 'about_page',\n 'services_page',\n 'booking_page',\n 'location_page',\n 'pricing_page',\n 'faq_page',\n 'testimonials_page',\n] as const;\n\nexport type SpecificPageName = (typeof SPECIFIC_PAGE_NAMES)[number];\n\nexport const specificPageNameSchema = z.enum(SPECIFIC_PAGE_NAMES);\n\nexport const specificPageVisitMetadataSchema = z\n .object({\n page_name: specificPageNameSchema,\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type SpecificPageVisitMetadata = z.infer<typeof specificPageVisitMetadataSchema>;\n\nexport const specificPageVisitConfigSchema = z\n .object({\n pages: z\n .array(\n z.object({\n name: specificPageNameSchema,\n pathPattern: z.custom<RegExp>((value) => value instanceof RegExp, {\n message: 'pathPattern must be a RegExp',\n }),\n }),\n )\n .min(1),\n })\n .strict();\n\nexport type SpecificPageVisitConfig = z.infer<typeof specificPageVisitConfigSchema>;\n","import { z } from 'zod';\n\n// time_on_site is automatic. The SDK starts a visibility-aware timer on page\n// load and fires once when the visible-time threshold is crossed. The timer\n// pauses when the tab goes hidden so a user who walks away from a tab\n// doesn't generate fake engagement.\n\nexport const timeOnSiteMetadataSchema = z\n .object({\n duration_ms: z.number().int().nonnegative(),\n page: z.object({\n path: z.string(),\n }),\n })\n .strict();\n\nexport type TimeOnSiteMetadata = z.infer<typeof timeOnSiteMetadataSchema>;\n\nexport const timeOnSiteConfigSchema = z\n .object({\n thresholdSeconds: z.number().int().positive(),\n })\n .strict();\nexport type TimeOnSiteConfig = z.infer<typeof timeOnSiteConfigSchema>;\n","import type { z } from 'zod';\n\nimport {\n ctaClickConfigSchema,\n ctaClickMetadataSchema,\n type CtaClickConfig,\n type CtaClickMetadata,\n} from './cta-click';\nimport {\n formStartConfigSchema,\n formStartMetadataSchema,\n type FormStartConfig,\n type FormStartMetadata,\n} from './form-start';\nimport {\n formSubmitConfigSchema,\n formSubmitMetadataSchema,\n type FormSubmitConfig,\n type FormSubmitMetadata,\n} from './form-submit';\nimport {\n multiPageSessionConfigSchema,\n multiPageSessionMetadataSchema,\n type MultiPageSessionConfig,\n type MultiPageSessionMetadata,\n} from './multi-page-session';\nimport {\n pageViewConfigSchema,\n pageViewMetadataSchema,\n type PageViewConfig,\n type PageViewMetadata,\n} from './page-view';\nimport {\n phoneClickConfigSchema,\n phoneClickMetadataSchema,\n type PhoneClickConfig,\n type PhoneClickMetadata,\n} from './phone-click';\nimport {\n scrollDepthConfigSchema,\n scrollDepthMetadataSchema,\n type ScrollDepthConfig,\n type ScrollDepthMetadata,\n} from './scroll-depth';\nimport {\n specificPageVisitConfigSchema,\n specificPageVisitMetadataSchema,\n type SpecificPageVisitConfig,\n type SpecificPageVisitMetadata,\n} from './specific-page-visit';\nimport {\n timeOnSiteConfigSchema,\n timeOnSiteMetadataSchema,\n type TimeOnSiteConfig,\n type TimeOnSiteMetadata,\n} from './time-on-site';\n\n// Event kind — automatic events are fired by the SDK itself when their\n// client-side signal fires (page_view on navigation, time_on_site on timer,\n// etc). Manual events are only fireable via explicit consumer code.\nexport type EventKind = 'automatic' | 'manual';\n\n// The registry is the single client-side source of truth for \"what events\n// exist, what shape does their metadata take, and what kind are they\". The\n// backend mirror lives in apps/api/src/schemas/tracking_events.py and is\n// kept in sync via the drift test in apps/api/tests/test_event_schema_drift.py.\nexport const EVENT_REGISTRY = {\n // --- automatic triggers ---\n page_view: {\n kind: 'automatic',\n metadataSchema: pageViewMetadataSchema,\n configSchema: pageViewConfigSchema,\n },\n time_on_site: {\n kind: 'automatic',\n metadataSchema: timeOnSiteMetadataSchema,\n configSchema: timeOnSiteConfigSchema,\n },\n specific_page_visit: {\n kind: 'automatic',\n metadataSchema: specificPageVisitMetadataSchema,\n configSchema: specificPageVisitConfigSchema,\n },\n scroll_depth: {\n kind: 'automatic',\n metadataSchema: scrollDepthMetadataSchema,\n configSchema: scrollDepthConfigSchema,\n },\n multi_page_session: {\n kind: 'automatic',\n metadataSchema: multiPageSessionMetadataSchema,\n configSchema: multiPageSessionConfigSchema,\n },\n form_start: {\n kind: 'automatic',\n metadataSchema: formStartMetadataSchema,\n configSchema: formStartConfigSchema,\n },\n // --- manual triggers ---\n form_submit: {\n kind: 'manual',\n metadataSchema: formSubmitMetadataSchema,\n configSchema: formSubmitConfigSchema,\n },\n phone_click: {\n kind: 'manual',\n metadataSchema: phoneClickMetadataSchema,\n configSchema: phoneClickConfigSchema,\n },\n cta_click: {\n kind: 'manual',\n metadataSchema: ctaClickMetadataSchema,\n configSchema: ctaClickConfigSchema,\n },\n} as const;\n\nexport type EventName = keyof typeof EVENT_REGISTRY;\n\n// Narrow the event name union based on `kind`.\nexport type AutomaticEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'automatic' ? K : never;\n}[EventName];\n\nexport type ManualEventName = {\n [K in EventName]: (typeof EVENT_REGISTRY)[K]['kind'] extends 'manual' ? K : never;\n}[EventName];\n\n// Map an event name to its explicit metadata / config TS type. We pull the\n// inferred types directly from the per-event files rather than deriving\n// via `z.infer<typeof EVENT_REGISTRY[K]['metadataSchema']>` so that error\n// messages name the event-specific type (PageViewMetadata, not an\n// anonymous zod inference).\ntype MetadataByName = {\n page_view: PageViewMetadata;\n time_on_site: TimeOnSiteMetadata;\n specific_page_visit: SpecificPageVisitMetadata;\n scroll_depth: ScrollDepthMetadata;\n multi_page_session: MultiPageSessionMetadata;\n form_start: FormStartMetadata;\n form_submit: FormSubmitMetadata;\n phone_click: PhoneClickMetadata;\n cta_click: CtaClickMetadata;\n};\n\ntype ConfigByName = {\n page_view: PageViewConfig;\n time_on_site: TimeOnSiteConfig;\n specific_page_visit: SpecificPageVisitConfig;\n scroll_depth: ScrollDepthConfig;\n multi_page_session: MultiPageSessionConfig;\n form_start: FormStartConfig;\n form_submit: FormSubmitConfig;\n phone_click: PhoneClickConfig;\n cta_click: CtaClickConfig;\n};\n\nexport type EventMetadata<K extends EventName> = MetadataByName[K];\nexport type EventConfig<K extends EventName> = ConfigByName[K];\n\n// Runtime constant arrays for iteration at consumer / factory time.\nexport const ALL_AUTOMATIC_EVENT_NAMES: readonly AutomaticEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'automatic')\n .map(([name]) => name) as AutomaticEventName[];\n\nexport const ALL_MANUAL_EVENT_NAMES: readonly ManualEventName[] = (\n Object.entries(EVENT_REGISTRY) as Array<[EventName, (typeof EVENT_REGISTRY)[EventName]]>\n)\n .filter(([, def]) => def.kind === 'manual')\n .map(([name]) => name) as ManualEventName[];\n\n// The shape passed to createTracking({ triggers: ... }).\n//\n// `automatic.page_view` is **required** — if you're installing tracking you\n// always want page views, and forgetting to register it is a footgun we\n// prefer to catch at compile time. Every other automatic event is optional.\n// Manual events are all optional — register only the ones you actually\n// intend to fire from consumer code.\nexport type TriggerRegistryConfig = {\n automatic: {\n page_view: EventConfig<'page_view'>;\n } & Partial<{\n time_on_site: EventConfig<'time_on_site'>;\n specific_page_visit: EventConfig<'specific_page_visit'>;\n scroll_depth: EventConfig<'scroll_depth'>;\n multi_page_session: EventConfig<'multi_page_session'>;\n form_start: EventConfig<'form_start'>;\n }>;\n manual?: Partial<{\n form_submit: EventConfig<'form_submit'>;\n phone_click: EventConfig<'phone_click'>;\n cta_click: EventConfig<'cta_click'>;\n }>;\n};\n\n// Given a registry, which manual event names did the consumer actually\n// register? These are the only ones `trackEvent()` will accept as its\n// first argument. `NonNullable` collapses the case where `manual` was\n// omitted entirely.\nexport type RegisteredManualEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof NonNullable<TRegistry['manual']>, ManualEventName>;\n\n// Given a registry, which automatic event names did the consumer register?\n// Used by the factory to decide which auto-trigger attach helpers to wire up.\nexport type RegisteredAutomaticEvents<TRegistry extends TriggerRegistryConfig> =\n Extract<keyof TRegistry['automatic'], AutomaticEventName>;\n\n// Internal helper used by the factory: given a registry, return the full\n// discriminated union of valid (eventName, metadata) pairs the consumer\n// can pass to trackEvent().\nexport type TrackableEvent<TRegistry extends TriggerRegistryConfig> = {\n [K in RegisteredManualEvents<TRegistry>]: {\n eventType: K;\n metadata: EventMetadata<K>;\n };\n}[RegisteredManualEvents<TRegistry>];\n\n// Runtime helper: look up a schema pair by name. Cast through `unknown`\n// because the registry is `as const` and TS loses the specific schema type\n// when indexing via a dynamic key.\nexport function getEventDefinition(name: EventName): {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n} {\n return EVENT_REGISTRY[name] as unknown as {\n kind: EventKind;\n metadataSchema: z.ZodTypeAny;\n configSchema: z.ZodTypeAny;\n };\n}\n"],"mappings":"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","names":["import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod"]}
|
package/dist/middleware.mjs
CHANGED
|
@@ -45,26 +45,21 @@ var pageViewMetadataSchema = z.object({
|
|
|
45
45
|
}).strict();
|
|
46
46
|
var pageViewConfigSchema = z.object({}).strict();
|
|
47
47
|
|
|
48
|
-
// ../tracking-core/src/events/
|
|
48
|
+
// ../tracking-core/src/events/cta-click.ts
|
|
49
49
|
import { z as z2 } from "zod";
|
|
50
|
-
var
|
|
50
|
+
var ctaClickMetadataSchema = z2.object({
|
|
51
|
+
cta_name: z2.string(),
|
|
51
52
|
page: z2.object({
|
|
52
53
|
path: z2.string()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
|
|
57
|
-
// real regex at createTracking() time; the factory validates with this
|
|
58
|
-
// schema and then keeps the live RegExp reference for runtime matching.
|
|
59
|
-
pathPattern: z2.custom(
|
|
60
|
-
(value) => value instanceof RegExp,
|
|
61
|
-
{ message: "pathPattern must be a RegExp" }
|
|
62
|
-
)
|
|
54
|
+
}),
|
|
55
|
+
section: z2.string().nullable().optional(),
|
|
56
|
+
destination_url: z2.string().nullable().optional()
|
|
63
57
|
}).strict();
|
|
58
|
+
var ctaClickConfigSchema = z2.object({}).strict();
|
|
64
59
|
|
|
65
|
-
// ../tracking-core/src/events/form-
|
|
60
|
+
// ../tracking-core/src/events/form-start.ts
|
|
66
61
|
import { z as z3 } from "zod";
|
|
67
|
-
var
|
|
62
|
+
var formStartMetadataSchema = z3.object({
|
|
68
63
|
form: z3.object({
|
|
69
64
|
id: z3.string(),
|
|
70
65
|
action: z3.string().nullable()
|
|
@@ -73,32 +68,106 @@ var formSubmitMetadataSchema = z3.object({
|
|
|
73
68
|
path: z3.string()
|
|
74
69
|
})
|
|
75
70
|
}).strict();
|
|
76
|
-
var
|
|
71
|
+
var formStartConfigSchema = z3.object({
|
|
72
|
+
selector: z3.string().optional()
|
|
73
|
+
}).strict();
|
|
77
74
|
|
|
78
|
-
// ../tracking-core/src/events/
|
|
75
|
+
// ../tracking-core/src/events/form-submit.ts
|
|
79
76
|
import { z as z4 } from "zod";
|
|
80
|
-
var
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
var formSubmitMetadataSchema = z4.object({
|
|
78
|
+
form: z4.object({
|
|
79
|
+
id: z4.string(),
|
|
80
|
+
action: z4.string().nullable(),
|
|
81
|
+
fields: z4.array(
|
|
82
|
+
z4.object({
|
|
83
|
+
name: z4.string(),
|
|
84
|
+
type: z4.string(),
|
|
85
|
+
label: z4.string().nullable(),
|
|
86
|
+
has_value: z4.boolean()
|
|
87
|
+
})
|
|
88
|
+
).optional()
|
|
85
89
|
}),
|
|
86
90
|
page: z4.object({
|
|
87
91
|
path: z4.string()
|
|
88
92
|
})
|
|
89
93
|
}).strict();
|
|
90
|
-
var
|
|
94
|
+
var formSubmitConfigSchema = z4.object({}).strict();
|
|
91
95
|
|
|
92
|
-
// ../tracking-core/src/events/
|
|
96
|
+
// ../tracking-core/src/events/multi-page-session.ts
|
|
93
97
|
import { z as z5 } from "zod";
|
|
94
|
-
var
|
|
95
|
-
|
|
98
|
+
var multiPageSessionMetadataSchema = z5.object({
|
|
99
|
+
page_count: z5.number().int().min(2),
|
|
96
100
|
page: z5.object({
|
|
97
101
|
path: z5.string()
|
|
98
102
|
})
|
|
99
103
|
}).strict();
|
|
100
|
-
var
|
|
101
|
-
|
|
104
|
+
var multiPageSessionConfigSchema = z5.object({
|
|
105
|
+
pageThreshold: z5.number().int().min(2)
|
|
106
|
+
}).strict();
|
|
107
|
+
|
|
108
|
+
// ../tracking-core/src/events/phone-click.ts
|
|
109
|
+
import { z as z6 } from "zod";
|
|
110
|
+
var phoneClickMetadataSchema = z6.object({
|
|
111
|
+
phone_number: z6.string(),
|
|
112
|
+
page: z6.object({
|
|
113
|
+
path: z6.string()
|
|
114
|
+
}),
|
|
115
|
+
section: z6.string().nullable().optional()
|
|
116
|
+
}).strict();
|
|
117
|
+
var phoneClickConfigSchema = z6.object({}).strict();
|
|
118
|
+
|
|
119
|
+
// ../tracking-core/src/events/scroll-depth.ts
|
|
120
|
+
import { z as z7 } from "zod";
|
|
121
|
+
var scrollDepthMetadataSchema = z7.object({
|
|
122
|
+
depth_percent: z7.number().int().min(1).max(100),
|
|
123
|
+
page: z7.object({
|
|
124
|
+
path: z7.string()
|
|
125
|
+
})
|
|
126
|
+
}).strict();
|
|
127
|
+
var scrollDepthConfigSchema = z7.object({
|
|
128
|
+
thresholds: z7.array(z7.number().int().min(1).max(100)).min(1)
|
|
129
|
+
}).strict();
|
|
130
|
+
|
|
131
|
+
// ../tracking-core/src/events/specific-page-visit.ts
|
|
132
|
+
import { z as z8 } from "zod";
|
|
133
|
+
var SPECIFIC_PAGE_NAMES = [
|
|
134
|
+
"contact_page",
|
|
135
|
+
"about_page",
|
|
136
|
+
"services_page",
|
|
137
|
+
"booking_page",
|
|
138
|
+
"location_page",
|
|
139
|
+
"pricing_page",
|
|
140
|
+
"faq_page",
|
|
141
|
+
"testimonials_page"
|
|
142
|
+
];
|
|
143
|
+
var specificPageNameSchema = z8.enum(SPECIFIC_PAGE_NAMES);
|
|
144
|
+
var specificPageVisitMetadataSchema = z8.object({
|
|
145
|
+
page_name: specificPageNameSchema,
|
|
146
|
+
page: z8.object({
|
|
147
|
+
path: z8.string()
|
|
148
|
+
})
|
|
149
|
+
}).strict();
|
|
150
|
+
var specificPageVisitConfigSchema = z8.object({
|
|
151
|
+
pages: z8.array(
|
|
152
|
+
z8.object({
|
|
153
|
+
name: specificPageNameSchema,
|
|
154
|
+
pathPattern: z8.custom((value) => value instanceof RegExp, {
|
|
155
|
+
message: "pathPattern must be a RegExp"
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
).min(1)
|
|
159
|
+
}).strict();
|
|
160
|
+
|
|
161
|
+
// ../tracking-core/src/events/time-on-site.ts
|
|
162
|
+
import { z as z9 } from "zod";
|
|
163
|
+
var timeOnSiteMetadataSchema = z9.object({
|
|
164
|
+
duration_ms: z9.number().int().nonnegative(),
|
|
165
|
+
page: z9.object({
|
|
166
|
+
path: z9.string()
|
|
167
|
+
})
|
|
168
|
+
}).strict();
|
|
169
|
+
var timeOnSiteConfigSchema = z9.object({
|
|
170
|
+
thresholdSeconds: z9.number().int().positive()
|
|
102
171
|
}).strict();
|
|
103
172
|
|
|
104
173
|
// ../tracking-core/src/events/registry.ts
|
|
@@ -114,10 +183,25 @@ var EVENT_REGISTRY = {
|
|
|
114
183
|
metadataSchema: timeOnSiteMetadataSchema,
|
|
115
184
|
configSchema: timeOnSiteConfigSchema
|
|
116
185
|
},
|
|
117
|
-
|
|
186
|
+
specific_page_visit: {
|
|
118
187
|
kind: "automatic",
|
|
119
|
-
metadataSchema:
|
|
120
|
-
configSchema:
|
|
188
|
+
metadataSchema: specificPageVisitMetadataSchema,
|
|
189
|
+
configSchema: specificPageVisitConfigSchema
|
|
190
|
+
},
|
|
191
|
+
scroll_depth: {
|
|
192
|
+
kind: "automatic",
|
|
193
|
+
metadataSchema: scrollDepthMetadataSchema,
|
|
194
|
+
configSchema: scrollDepthConfigSchema
|
|
195
|
+
},
|
|
196
|
+
multi_page_session: {
|
|
197
|
+
kind: "automatic",
|
|
198
|
+
metadataSchema: multiPageSessionMetadataSchema,
|
|
199
|
+
configSchema: multiPageSessionConfigSchema
|
|
200
|
+
},
|
|
201
|
+
form_start: {
|
|
202
|
+
kind: "automatic",
|
|
203
|
+
metadataSchema: formStartMetadataSchema,
|
|
204
|
+
configSchema: formStartConfigSchema
|
|
121
205
|
},
|
|
122
206
|
// --- manual triggers ---
|
|
123
207
|
form_submit: {
|
|
@@ -129,6 +213,11 @@ var EVENT_REGISTRY = {
|
|
|
129
213
|
kind: "manual",
|
|
130
214
|
metadataSchema: phoneClickMetadataSchema,
|
|
131
215
|
configSchema: phoneClickConfigSchema
|
|
216
|
+
},
|
|
217
|
+
cta_click: {
|
|
218
|
+
kind: "manual",
|
|
219
|
+
metadataSchema: ctaClickMetadataSchema,
|
|
220
|
+
configSchema: ctaClickConfigSchema
|
|
132
221
|
}
|
|
133
222
|
};
|
|
134
223
|
var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
|