@aranova/tracking-next 0.2.3 → 0.4.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 +499 -5
- package/dist/index.d.ts +499 -5
- package/dist/index.js +604 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +609 -30
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +112 -0
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +112 -0
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.d.mts +5 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.js +112 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +112 -0
- package/dist/server.mjs.map +1 -1
- package/dist/{server-BMTa7njW.d.mts → types-Gp0ioRiQ.d.mts} +3 -3
- package/dist/{server-BMTa7njW.d.ts → types-Gp0ioRiQ.d.ts} +3 -3
- package/package.json +3 -2
package/dist/server.d.mts
CHANGED
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -57,6 +57,118 @@ function getTrackingParamsFromCookieReader(readCookie) {
|
|
|
57
57
|
}, createEmptyTrackingParams());
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// ../tracking-core/src/session.ts
|
|
61
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
62
|
+
|
|
63
|
+
// ../tracking-core/src/events/page-view.ts
|
|
64
|
+
var import_zod = require("zod");
|
|
65
|
+
var pageViewMetadataSchema = import_zod.z.object({
|
|
66
|
+
page: import_zod.z.object({
|
|
67
|
+
title: import_zod.z.string().nullable(),
|
|
68
|
+
path: import_zod.z.string(),
|
|
69
|
+
search: import_zod.z.string(),
|
|
70
|
+
hash: import_zod.z.string()
|
|
71
|
+
}),
|
|
72
|
+
referrer: import_zod.z.string().nullable(),
|
|
73
|
+
// `.nullable().optional()` — absent (undefined) OR explicit null OR a
|
|
74
|
+
// real viewport object. Mirrors Pydantic's `_Viewport | None = None`
|
|
75
|
+
// on the backend side so the drift test stays clean.
|
|
76
|
+
viewport: import_zod.z.object({
|
|
77
|
+
w: import_zod.z.number(),
|
|
78
|
+
h: import_zod.z.number()
|
|
79
|
+
}).nullable().optional()
|
|
80
|
+
}).strict();
|
|
81
|
+
var pageViewConfigSchema = import_zod.z.object({}).strict();
|
|
82
|
+
|
|
83
|
+
// ../tracking-core/src/events/contact-page-visit.ts
|
|
84
|
+
var import_zod2 = require("zod");
|
|
85
|
+
var contactPageVisitMetadataSchema = import_zod2.z.object({
|
|
86
|
+
page: import_zod2.z.object({
|
|
87
|
+
path: import_zod2.z.string()
|
|
88
|
+
})
|
|
89
|
+
}).strict();
|
|
90
|
+
var contactPageVisitConfigSchema = import_zod2.z.object({
|
|
91
|
+
// RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
|
|
92
|
+
// real regex at createTracking() time; the factory validates with this
|
|
93
|
+
// schema and then keeps the live RegExp reference for runtime matching.
|
|
94
|
+
pathPattern: import_zod2.z.custom(
|
|
95
|
+
(value) => value instanceof RegExp,
|
|
96
|
+
{ message: "pathPattern must be a RegExp" }
|
|
97
|
+
)
|
|
98
|
+
}).strict();
|
|
99
|
+
|
|
100
|
+
// ../tracking-core/src/events/form-submit.ts
|
|
101
|
+
var import_zod3 = require("zod");
|
|
102
|
+
var formSubmitMetadataSchema = import_zod3.z.object({
|
|
103
|
+
form: import_zod3.z.object({
|
|
104
|
+
id: import_zod3.z.string(),
|
|
105
|
+
action: import_zod3.z.string().nullable()
|
|
106
|
+
}),
|
|
107
|
+
page: import_zod3.z.object({
|
|
108
|
+
path: import_zod3.z.string()
|
|
109
|
+
})
|
|
110
|
+
}).strict();
|
|
111
|
+
var formSubmitConfigSchema = import_zod3.z.object({}).strict();
|
|
112
|
+
|
|
113
|
+
// ../tracking-core/src/events/phone-click.ts
|
|
114
|
+
var import_zod4 = require("zod");
|
|
115
|
+
var phoneClickMetadataSchema = import_zod4.z.object({
|
|
116
|
+
element: import_zod4.z.object({
|
|
117
|
+
tag: import_zod4.z.string(),
|
|
118
|
+
text: import_zod4.z.string().nullable(),
|
|
119
|
+
href: import_zod4.z.string()
|
|
120
|
+
}),
|
|
121
|
+
page: import_zod4.z.object({
|
|
122
|
+
path: import_zod4.z.string()
|
|
123
|
+
})
|
|
124
|
+
}).strict();
|
|
125
|
+
var phoneClickConfigSchema = import_zod4.z.object({}).strict();
|
|
126
|
+
|
|
127
|
+
// ../tracking-core/src/events/time-on-site.ts
|
|
128
|
+
var import_zod5 = require("zod");
|
|
129
|
+
var timeOnSiteMetadataSchema = import_zod5.z.object({
|
|
130
|
+
duration_ms: import_zod5.z.number().int().nonnegative(),
|
|
131
|
+
page: import_zod5.z.object({
|
|
132
|
+
path: import_zod5.z.string()
|
|
133
|
+
})
|
|
134
|
+
}).strict();
|
|
135
|
+
var timeOnSiteConfigSchema = import_zod5.z.object({
|
|
136
|
+
thresholdSeconds: import_zod5.z.number().int().positive()
|
|
137
|
+
}).strict();
|
|
138
|
+
|
|
139
|
+
// ../tracking-core/src/events/registry.ts
|
|
140
|
+
var EVENT_REGISTRY = {
|
|
141
|
+
// --- automatic triggers ---
|
|
142
|
+
page_view: {
|
|
143
|
+
kind: "automatic",
|
|
144
|
+
metadataSchema: pageViewMetadataSchema,
|
|
145
|
+
configSchema: pageViewConfigSchema
|
|
146
|
+
},
|
|
147
|
+
time_on_site: {
|
|
148
|
+
kind: "automatic",
|
|
149
|
+
metadataSchema: timeOnSiteMetadataSchema,
|
|
150
|
+
configSchema: timeOnSiteConfigSchema
|
|
151
|
+
},
|
|
152
|
+
contact_page_visit: {
|
|
153
|
+
kind: "automatic",
|
|
154
|
+
metadataSchema: contactPageVisitMetadataSchema,
|
|
155
|
+
configSchema: contactPageVisitConfigSchema
|
|
156
|
+
},
|
|
157
|
+
// --- manual triggers ---
|
|
158
|
+
form_submit: {
|
|
159
|
+
kind: "manual",
|
|
160
|
+
metadataSchema: formSubmitMetadataSchema,
|
|
161
|
+
configSchema: formSubmitConfigSchema
|
|
162
|
+
},
|
|
163
|
+
phone_click: {
|
|
164
|
+
kind: "manual",
|
|
165
|
+
metadataSchema: phoneClickMetadataSchema,
|
|
166
|
+
configSchema: phoneClickConfigSchema
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
|
|
170
|
+
var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
|
|
171
|
+
|
|
60
172
|
// src/server.ts
|
|
61
173
|
function getCookieStore() {
|
|
62
174
|
return (0, import_headers.cookies)();
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts"],"sourcesContent":["import 'server-only';\n\nimport { cookies } from 'next/headers';\n\nimport { getTrackingParamsFromCookieReader, type TrackingParams } from '../../tracking-core/src/index';\n\ninterface CookieValue {\n value: string;\n}\n\ninterface CookieStore {\n get(name: string): CookieValue | undefined;\n}\n\nfunction getCookieStore(): CookieStore {\n return cookies() as unknown as CookieStore;\n}\n\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAO;AAEP,qBAAwB;;;ACEjB,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,SAAS,4BAA4C;AAC1D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AACF;AAEO,SAAS,6BAA6B,OAA+B;AAC1E,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEO,SAAS,kCACd,YACgB;AAChB,SAAO,oBAAoB,OAAuB,CAAC,QAAQ,QAAQ;AACjE,WAAO,GAAG,IAAI,6BAA6B,WAAW,GAAG,CAAC;AAC1D,WAAO;AAAA,EACT,GAAG,0BAA0B,CAAC;AAChC;;;ADzBA,SAAS,iBAA8B;AACrC,aAAO,wBAAQ;AACjB;AAEO,SAAS,0BAA0C;AACxD,QAAM,cAAc,eAAe;AACnC,SAAO,kCAAkC,CAAC,QAAQ,YAAY,IAAI,GAAG,GAAG,KAAK;AAC/E;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/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 { cookies } from 'next/headers';\n\nimport { getTrackingParamsFromCookieReader, type TrackingParams } from '../../tracking-core/src/index';\n\ninterface CookieValue {\n value: string;\n}\n\ninterface CookieStore {\n get(name: string): CookieValue | undefined;\n}\n\nfunction getCookieStore(): CookieStore {\n return cookies() as unknown as CookieStore;\n}\n\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport 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"]}
|
package/dist/server.mjs
CHANGED
|
@@ -33,6 +33,118 @@ function getTrackingParamsFromCookieReader(readCookie) {
|
|
|
33
33
|
}, createEmptyTrackingParams());
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// ../tracking-core/src/session.ts
|
|
37
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
38
|
+
|
|
39
|
+
// ../tracking-core/src/events/page-view.ts
|
|
40
|
+
import { z } from "zod";
|
|
41
|
+
var pageViewMetadataSchema = z.object({
|
|
42
|
+
page: z.object({
|
|
43
|
+
title: z.string().nullable(),
|
|
44
|
+
path: z.string(),
|
|
45
|
+
search: z.string(),
|
|
46
|
+
hash: z.string()
|
|
47
|
+
}),
|
|
48
|
+
referrer: z.string().nullable(),
|
|
49
|
+
// `.nullable().optional()` — absent (undefined) OR explicit null OR a
|
|
50
|
+
// real viewport object. Mirrors Pydantic's `_Viewport | None = None`
|
|
51
|
+
// on the backend side so the drift test stays clean.
|
|
52
|
+
viewport: z.object({
|
|
53
|
+
w: z.number(),
|
|
54
|
+
h: z.number()
|
|
55
|
+
}).nullable().optional()
|
|
56
|
+
}).strict();
|
|
57
|
+
var pageViewConfigSchema = z.object({}).strict();
|
|
58
|
+
|
|
59
|
+
// ../tracking-core/src/events/contact-page-visit.ts
|
|
60
|
+
import { z as z2 } from "zod";
|
|
61
|
+
var contactPageVisitMetadataSchema = z2.object({
|
|
62
|
+
page: z2.object({
|
|
63
|
+
path: z2.string()
|
|
64
|
+
})
|
|
65
|
+
}).strict();
|
|
66
|
+
var contactPageVisitConfigSchema = z2.object({
|
|
67
|
+
// RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
|
|
68
|
+
// real regex at createTracking() time; the factory validates with this
|
|
69
|
+
// schema and then keeps the live RegExp reference for runtime matching.
|
|
70
|
+
pathPattern: z2.custom(
|
|
71
|
+
(value) => value instanceof RegExp,
|
|
72
|
+
{ message: "pathPattern must be a RegExp" }
|
|
73
|
+
)
|
|
74
|
+
}).strict();
|
|
75
|
+
|
|
76
|
+
// ../tracking-core/src/events/form-submit.ts
|
|
77
|
+
import { z as z3 } from "zod";
|
|
78
|
+
var formSubmitMetadataSchema = z3.object({
|
|
79
|
+
form: z3.object({
|
|
80
|
+
id: z3.string(),
|
|
81
|
+
action: z3.string().nullable()
|
|
82
|
+
}),
|
|
83
|
+
page: z3.object({
|
|
84
|
+
path: z3.string()
|
|
85
|
+
})
|
|
86
|
+
}).strict();
|
|
87
|
+
var formSubmitConfigSchema = z3.object({}).strict();
|
|
88
|
+
|
|
89
|
+
// ../tracking-core/src/events/phone-click.ts
|
|
90
|
+
import { z as z4 } from "zod";
|
|
91
|
+
var phoneClickMetadataSchema = z4.object({
|
|
92
|
+
element: z4.object({
|
|
93
|
+
tag: z4.string(),
|
|
94
|
+
text: z4.string().nullable(),
|
|
95
|
+
href: z4.string()
|
|
96
|
+
}),
|
|
97
|
+
page: z4.object({
|
|
98
|
+
path: z4.string()
|
|
99
|
+
})
|
|
100
|
+
}).strict();
|
|
101
|
+
var phoneClickConfigSchema = z4.object({}).strict();
|
|
102
|
+
|
|
103
|
+
// ../tracking-core/src/events/time-on-site.ts
|
|
104
|
+
import { z as z5 } from "zod";
|
|
105
|
+
var timeOnSiteMetadataSchema = z5.object({
|
|
106
|
+
duration_ms: z5.number().int().nonnegative(),
|
|
107
|
+
page: z5.object({
|
|
108
|
+
path: z5.string()
|
|
109
|
+
})
|
|
110
|
+
}).strict();
|
|
111
|
+
var timeOnSiteConfigSchema = z5.object({
|
|
112
|
+
thresholdSeconds: z5.number().int().positive()
|
|
113
|
+
}).strict();
|
|
114
|
+
|
|
115
|
+
// ../tracking-core/src/events/registry.ts
|
|
116
|
+
var EVENT_REGISTRY = {
|
|
117
|
+
// --- automatic triggers ---
|
|
118
|
+
page_view: {
|
|
119
|
+
kind: "automatic",
|
|
120
|
+
metadataSchema: pageViewMetadataSchema,
|
|
121
|
+
configSchema: pageViewConfigSchema
|
|
122
|
+
},
|
|
123
|
+
time_on_site: {
|
|
124
|
+
kind: "automatic",
|
|
125
|
+
metadataSchema: timeOnSiteMetadataSchema,
|
|
126
|
+
configSchema: timeOnSiteConfigSchema
|
|
127
|
+
},
|
|
128
|
+
contact_page_visit: {
|
|
129
|
+
kind: "automatic",
|
|
130
|
+
metadataSchema: contactPageVisitMetadataSchema,
|
|
131
|
+
configSchema: contactPageVisitConfigSchema
|
|
132
|
+
},
|
|
133
|
+
// --- manual triggers ---
|
|
134
|
+
form_submit: {
|
|
135
|
+
kind: "manual",
|
|
136
|
+
metadataSchema: formSubmitMetadataSchema,
|
|
137
|
+
configSchema: formSubmitConfigSchema
|
|
138
|
+
},
|
|
139
|
+
phone_click: {
|
|
140
|
+
kind: "manual",
|
|
141
|
+
metadataSchema: phoneClickMetadataSchema,
|
|
142
|
+
configSchema: phoneClickConfigSchema
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
|
|
146
|
+
var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
|
|
147
|
+
|
|
36
148
|
// src/server.ts
|
|
37
149
|
function getCookieStore() {
|
|
38
150
|
return cookies();
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts"],"sourcesContent":["import 'server-only';\n\nimport { cookies } from 'next/headers';\n\nimport { getTrackingParamsFromCookieReader, type TrackingParams } from '../../tracking-core/src/index';\n\ninterface CookieValue {\n value: string;\n}\n\ninterface CookieStore {\n get(name: string): CookieValue | undefined;\n}\n\nfunction getCookieStore(): CookieStore {\n return cookies() as unknown as CookieStore;\n}\n\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n"],"mappings":";AAAA,OAAO;AAEP,SAAS,eAAe;;;ACEjB,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,SAAS,4BAA4C;AAC1D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AACF;AAEO,SAAS,6BAA6B,OAA+B;AAC1E,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEO,SAAS,kCACd,YACgB;AAChB,SAAO,oBAAoB,OAAuB,CAAC,QAAQ,QAAQ;AACjE,WAAO,GAAG,IAAI,6BAA6B,WAAW,GAAG,CAAC;AAC1D,WAAO;AAAA,EACT,GAAG,0BAA0B,CAAC;AAChC;;;ADzBA,SAAS,iBAA8B;AACrC,SAAO,QAAQ;AACjB;AAEO,SAAS,0BAA0C;AACxD,QAAM,cAAc,eAAe;AACnC,SAAO,kCAAkC,CAAC,QAAQ,YAAY,IAAI,GAAG,GAAG,KAAK;AAC/E;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/events/page-view.ts","../../tracking-core/src/events/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 { cookies } from 'next/headers';\n\nimport { getTrackingParamsFromCookieReader, type TrackingParams } from '../../tracking-core/src/index';\n\ninterface CookieValue {\n value: string;\n}\n\ninterface CookieStore {\n get(name: string): CookieValue | undefined;\n}\n\nfunction getCookieStore(): CookieStore {\n return cookies() as unknown as CookieStore;\n}\n\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport 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":["z","z","z","z"]}
|
|
@@ -19,6 +19,7 @@ interface TrackingClientContext {
|
|
|
19
19
|
}
|
|
20
20
|
interface TrackingSessionUpsertPayload {
|
|
21
21
|
session_id: string;
|
|
22
|
+
visitor_id: string | null;
|
|
22
23
|
gclid: string | null;
|
|
23
24
|
fbclid: string | null;
|
|
24
25
|
utm_source: string | null;
|
|
@@ -58,10 +59,9 @@ declare global {
|
|
|
58
59
|
getTrackingParams: () => TrackingParams;
|
|
59
60
|
getConsentState: () => ConsentState;
|
|
60
61
|
setConsentState: (state: 'granted' | 'denied') => void;
|
|
62
|
+
trackEvent: (eventType: string, metadata: Record<string, unknown>) => void;
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export { type ConsentState as C, type TrackingInstallSurface as T, type TrackingClientContext as a, type TrackingParams as b, type TrackingEventCreatePayload as c, type TrackingSessionUpsertPayload as d, type TrackingInitConfig as e, getTrackingParamsServer as g };
|
|
67
|
+
export type { ConsentState as C, TrackingInstallSurface as T, TrackingClientContext as a, TrackingParams as b, TrackingEventCreatePayload as c, TrackingSessionUpsertPayload as d, TrackingInitConfig as e };
|
|
@@ -19,6 +19,7 @@ interface TrackingClientContext {
|
|
|
19
19
|
}
|
|
20
20
|
interface TrackingSessionUpsertPayload {
|
|
21
21
|
session_id: string;
|
|
22
|
+
visitor_id: string | null;
|
|
22
23
|
gclid: string | null;
|
|
23
24
|
fbclid: string | null;
|
|
24
25
|
utm_source: string | null;
|
|
@@ -58,10 +59,9 @@ declare global {
|
|
|
58
59
|
getTrackingParams: () => TrackingParams;
|
|
59
60
|
getConsentState: () => ConsentState;
|
|
60
61
|
setConsentState: (state: 'granted' | 'denied') => void;
|
|
62
|
+
trackEvent: (eventType: string, metadata: Record<string, unknown>) => void;
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export { type ConsentState as C, type TrackingInstallSurface as T, type TrackingClientContext as a, type TrackingParams as b, type TrackingEventCreatePayload as c, type TrackingSessionUpsertPayload as d, type TrackingInitConfig as e, getTrackingParamsServer as g };
|
|
67
|
+
export type { ConsentState as C, TrackingInstallSurface as T, TrackingClientContext as a, TrackingParams as b, TrackingEventCreatePayload as c, TrackingSessionUpsertPayload as d, TrackingInitConfig as e };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aranova/tracking-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Next.js tracking utilities for Aranova client sites",
|
|
6
6
|
"files": [
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"react": ">=18.0.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"server-only": "^0.0.1"
|
|
47
|
+
"server-only": "^0.0.1",
|
|
48
|
+
"zod": "^3.24.1"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@types/react": "^19.2.2",
|